ovto 0.4.1 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.gitmodules +3 -0
  4. data/CHANGELOG.md +15 -2
  5. data/Gemfile +2 -2
  6. data/Gemfile.lock +23 -27
  7. data/README.md +2 -2
  8. data/book/SUMMARY.md +3 -1
  9. data/book/api/actions.md +1 -1
  10. data/book/api/fetch.md +3 -3
  11. data/book/api/middleware.md +99 -0
  12. data/book/api/pure_component.md +30 -0
  13. data/book/guides/install.md +76 -0
  14. data/book/guides/tutorial.md +3 -3
  15. data/book/ovtologo.png +0 -0
  16. data/docs/api/Array.html +190 -0
  17. data/docs/api/Hash.html +196 -0
  18. data/docs/api/Ovto/Actions.html +132 -40
  19. data/docs/api/Ovto/App.html +254 -40
  20. data/docs/api/Ovto/Component/MoreThanOneNode.html +6 -6
  21. data/docs/api/Ovto/Component.html +102 -27
  22. data/docs/api/Ovto/Middleware/Actions.html +428 -0
  23. data/docs/api/Ovto/Middleware/Base.html +606 -0
  24. data/docs/api/Ovto/Middleware/Component.html +355 -0
  25. data/docs/api/Ovto/Middleware.html +288 -0
  26. data/docs/api/Ovto/PureComponent/StateIsNotAvailable.html +124 -0
  27. data/docs/api/Ovto/PureComponent.html +368 -0
  28. data/docs/api/Ovto/Runtime.html +7 -7
  29. data/docs/api/Ovto/State/MissingValue.html +6 -6
  30. data/docs/api/Ovto/State/{UnknownKey.html → UnknownStateKey.html} +11 -11
  31. data/docs/api/Ovto/State.html +59 -43
  32. data/docs/api/Ovto/WiredActionSet.html +636 -0
  33. data/docs/api/Ovto/WiredActions.html +78 -35
  34. data/docs/api/Ovto.html +226 -29
  35. data/docs/api/_index.html +99 -11
  36. data/docs/api/actions.html +36 -10
  37. data/docs/api/app.html +35 -9
  38. data/docs/api/class_list.html +3 -3
  39. data/docs/api/component.html +37 -11
  40. data/docs/api/css/style.css +2 -2
  41. data/docs/api/fetch.html +40 -14
  42. data/docs/api/file.README.html +8 -8
  43. data/docs/api/file_list.html +2 -2
  44. data/docs/api/frames.html +2 -2
  45. data/docs/api/index.html +8 -8
  46. data/docs/api/js/app.js +14 -3
  47. data/docs/api/method_list.html +318 -22
  48. data/docs/api/middleware.html +498 -0
  49. data/docs/api/pure_component.html +428 -0
  50. data/docs/api/state.html +35 -9
  51. data/docs/api/top-level-namespace.html +9 -7
  52. data/docs/gitbook/gitbook.js +2 -2
  53. data/docs/gitbook/theme.js +1 -1
  54. data/docs/guides/debugging.html +35 -9
  55. data/docs/guides/development.html +35 -9
  56. data/docs/guides/install.html +452 -0
  57. data/docs/guides/tutorial.html +101 -66
  58. data/docs/index.html +37 -17
  59. data/docs/ovtologo.png +0 -0
  60. data/docs/search_index.json +1 -1
  61. data/examples/sinatra/Gemfile +2 -0
  62. data/examples/sinatra/Gemfile.lock +33 -31
  63. data/examples/sinatra/app.rb +1 -4
  64. data/examples/sinatra/config.ru +6 -6
  65. data/examples/static/Gemfile.lock +10 -14
  66. data/lib/ovto/actions.rb +11 -0
  67. data/lib/ovto/app.rb +37 -13
  68. data/lib/ovto/component.rb +149 -73
  69. data/lib/ovto/fetch.rb +1 -1
  70. data/lib/ovto/middleware.rb +121 -0
  71. data/lib/ovto/pure_component.rb +22 -0
  72. data/lib/ovto/state.rb +13 -5
  73. data/lib/ovto/version.rb +1 -1
  74. data/lib/ovto/wired_action_set.rb +40 -0
  75. data/lib/ovto/wired_actions.rb +49 -11
  76. data/lib/ovto.rb +27 -0
  77. data/ovto.gemspec +1 -1
  78. metadata +35 -9
@@ -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,52 +1,52 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- ovto (0.4.1)
5
- opal (~> 0.11)
4
+ ovto (0.6.0)
5
+ opal (>= 0.11, < 2)
6
6
  rack (~> 2.0)
7
7
  thor (~> 0.20)
8
8
 
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.7)
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,27 +1,23 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- ovto (0.4.1)
5
- opal (~> 0.11)
4
+ ovto (0.6.0)
5
+ opal (>= 0.11, < 2)
6
6
  rack (~> 2.0)
7
7
  thor (~> 0.20)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- ast (2.4.0)
13
- hike (1.2.3)
12
+ ast (2.4.2)
14
13
  ifchanged (1.0.1)
15
- opal (0.11.4)
14
+ opal (1.2.0)
16
15
  ast (>= 2.3.0)
17
- hike (~> 1.2)
18
- parser (= 2.3.3.1)
19
- sourcemap (~> 0.1.0)
20
- parser (2.3.3.1)
21
- ast (~> 2.2)
22
- rack (2.0.7)
23
- rake (12.3.2)
24
- sourcemap (0.1.1)
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)
25
21
  thor (0.20.3)
26
22
 
27
23
  PLATFORMS
@@ -33,4 +29,4 @@ DEPENDENCIES
33
29
  rake
34
30
 
35
31
  BUNDLED WITH
36
- 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,18 @@ 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 = [[]]
26
+ @components = []
27
+ @components_index = 0
20
28
  end
21
29
 
22
30
  def render
@@ -24,7 +32,7 @@ module Ovto
24
32
  end
25
33
 
26
34
  def state
27
- @wired_actions._app.state
35
+ @wired_action_set.app.state
28
36
  end
29
37
 
30
38
  private
@@ -32,38 +40,38 @@ module Ovto
32
40
  # Render entire MyApp::MainComponent
33
41
  # Called from runtime.rb
34
42
  def render_view(state)
35
- do_render(state: state)
43
+ do_render({}, state)
36
44
  end
37
45
 
38
- def do_render(**args)
46
+ # Call #render to generate VDom
47
+ def do_render(args, state, &block)
39
48
  Ovto.debug_trace_log("rendering #{self}")
40
- @vdom_tree = []
49
+ @vdom_stack = [[]]
50
+ @components_index = 0
41
51
  @done_render = false
42
- @current_state = args[:state]
43
- parameters = method(:render).parameters
44
- if `!parameters` || parameters.nil? || accepts_state?(parameters)
45
- # We can pass `state:` safely
46
- return render(**args)
52
+ @current_state = 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]
47
65
  else
48
- # Remove `state:` keyword
49
- args_wo_state = args.reject{|k, v| k == :state}
50
- # Check it is empty (see https://github.com/opal/opal/issues/1872)
51
- return args_wo_state.empty? ? render() : render(**args_wo_state)
66
+ console.error("#render returned unknown value", rendered)
67
+ raise "#render returned unknown value"
52
68
  end
53
69
  end
54
70
 
55
- # Return true if the method accepts `state:` keyword
56
- def accepts_state?(parameters)
57
- parameters.each do |item|
58
- return true if item == [:key, :state] ||
59
- item == [:keyreq, :state] ||
60
- item[0] == :keyrest
61
- end
62
- return false
63
- end
64
-
65
71
  def actions
66
- @wired_actions
72
+ return @middleware_path.inject(@wired_action_set){|wa_set, middleware_name|
73
+ wa_set[middleware_name]
74
+ }[WiredActionSet::THE_MIDDLEWARE_ITSELF]
67
75
  end
68
76
 
69
77
  # o 'div', 'Hello.'
@@ -76,31 +84,39 @@ module Ovto
76
84
  # o 'h1', 'Hello.'
77
85
  # end
78
86
  # o 'div', `{nodeName: ....}` # Inject VDom spec directly
87
+ # o SubComponentClass
88
+ # o SubComponentClass do ... end # Ovto passes the block to SubComponent#render
79
89
  def o(_tag_name, arg1=nil, arg2=nil, &block)
80
- if native?(arg1)
90
+ if native?(arg1) # Embed VDom directly
81
91
  attributes = {}
82
92
  content = arg1
83
- elsif arg1.is_a?(Hash)
93
+ elsif arg1.is_a?(Hash) # Has attributes
84
94
  attributes = arg1
85
95
  content = arg2
86
- elsif arg2 == nil
96
+ elsif arg2 == nil # Has content instead of attributes, or both are nil
87
97
  attributes = {}
88
98
  content = arg1
89
99
  else
90
100
  raise ArgumentError
91
101
  end
92
102
 
93
- children = render_children(content, block)
94
103
  case _tag_name
95
104
  when Class
96
- 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)
97
109
  when 'text'
98
110
  unless attributes.empty?
99
111
  raise ArgumentError, "text cannot take attributes"
100
112
  end
101
113
  result = content
102
114
  when String
115
+ children = render_children(content, block)
103
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
104
120
  # Ignore nil/false
105
121
  more_attributes = attributes.reject{|k, v| !v}
106
122
  result = render_tag(tag_name, merge_attrs(base_attributes, more_attributes), children)
@@ -108,23 +124,15 @@ module Ovto
108
124
  raise TypeError, "tag_name must be a String or Component but got "+
109
125
  Ovto.inspect(tag_name)
110
126
  end
111
- if @vdom_tree.empty?
112
- if @done_render
113
- raise MoreThanOneNode, "#{self.class}#render must generate a single DOM node. Please wrap the tags with a 'div' or something."
114
- end
115
- @done_render = true
116
- return result
117
- else
118
- @vdom_tree.last.push(result)
119
- return @vdom_tree.last
120
- end
127
+ @vdom_stack.last.push(result)
128
+ return @vdom_stack.last
121
129
  end
122
130
 
123
131
  def extract_attrs(tag_name)
124
132
  case tag_name
125
- when /^([^.#]*)\.([-_\w]+)(\#([-_\w]+))?/ # a.b#c
133
+ when /^([^.#]*)\.([-\w]+)(\#([-\w]+))?/ # a.b#c
126
134
  tag_name, class_name, id = ($1.empty? ? 'div' : $1), $2, $4
127
- when /^([^.#]*)\#([-_\w]+)(\.([-_\w]+))?/ # a#b.c
135
+ when /^([^.#]*)\#([-\w]+)(\.([-\w]+))?/ # a#b.c
128
136
  tag_name, class_name, id = ($1.empty? ? 'div' : $1), $4, $2
129
137
  else
130
138
  class_name = id = nil
@@ -162,43 +170,111 @@ module Ovto
162
170
  [content.to_s]
163
171
  end
164
172
  when block
165
- @vdom_tree.push []
166
- block_value = block.call
167
- results = @vdom_tree.pop
168
- if results.length > 0 # 'o' was called at least once
169
- results
170
- elsif native?(block_value)
171
- # Inject VDom tree written in JS object
172
- # eg. Embed markdown
173
- [block_value]
174
- elsif block_value.is_a?(String)
175
- # When 'o' is never called in the child block, use the last value
176
- # eg.
177
- # o 'span' do
178
- # 'Hello' #=> This will be the content of the span tag
179
- # end
180
- [block_value]
181
- else
182
- # o 'div' do
183
- # # When items is `[]`, 'o' is never called and `block_value` will be `[]`
184
- # items.each{ o 'div', '...' }
185
- # end
186
- []
187
- end
173
+ render_block(block)
188
174
  else
189
175
  []
190
176
  end
191
177
  end
192
178
 
193
- def render_component(comp_class, args, children)
194
- comp = comp_class.new(@wired_actions)
195
- render_args = {state: @current_state}.merge(args)
196
- return comp.do_render(**render_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)
214
+ comp = new_component(comp_class)
215
+ orig_stack, @vdom_stack = @vdom_stack, [[]]
216
+ ret = comp.do_render(args, @current_state, &block)
217
+ @vdom_stack = orig_stack
218
+ ret
219
+ end
220
+
221
+ def new_component(comp_class)
222
+ comp = @components[@components_index]
223
+ if comp.is_a?(comp_class)
224
+ @components_index += 1
225
+ return comp
226
+ end
227
+
228
+ middleware_path = new_middleware_path(comp_class)
229
+ comp = @components[@components_index] = comp_class.new(@wired_action_set, middleware_path)
230
+ @components_index += 1
231
+ comp
232
+ end
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)
197
265
  end
198
266
 
199
267
  def render_tag(tag_name, attributes, children)
200
- js_attributes = Component.hash_to_js_obj(attributes || {})
201
- 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'])
202
278
  `js_attributes.style = #{Component.hash_to_js_obj(style)}`
203
279
  end
204
280
  children ||= `null`
data/lib/ovto/fetch.rb CHANGED
@@ -7,7 +7,7 @@ module Ovto
7
7
  # The server must respond a json text.
8
8
  #
9
9
  # Example:
10
- # Ovto.fetch('/api/new_task', 'POST', {title: "do something"}){|json_data|
10
+ # Ovto.fetch('/api/new_task', 'POST', {title: "do something"}).then{|json_data|
11
11
  # p json_data
12
12
  # }.fail{|e| # Network error, 404 Not Found, JSON parse error, etc.
13
13
  # p e