ovto 0.2.0

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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.gitmodules +3 -0
  4. data/CHANGELOG.md +22 -0
  5. data/Gemfile +7 -0
  6. data/Gemfile.lock +57 -0
  7. data/LICENSE.txt +44 -0
  8. data/README.md +84 -0
  9. data/Rakefile +41 -0
  10. data/book/README.md +1 -0
  11. data/book/SUMMARY.md +13 -0
  12. data/book/api/actions.md +77 -0
  13. data/book/api/app.md +86 -0
  14. data/book/api/component.md +175 -0
  15. data/book/api/fetch.md +42 -0
  16. data/book/api/state.md +97 -0
  17. data/book/guides/debugging.md +23 -0
  18. data/book/guides/development.md +11 -0
  19. data/book/guides/tutorial.md +288 -0
  20. data/book/screenshot.png +0 -0
  21. data/docs/api/Ovto/Actions.html +135 -0
  22. data/docs/api/Ovto/App.html +531 -0
  23. data/docs/api/Ovto/Component/MoreThanOneNode.html +135 -0
  24. data/docs/api/Ovto/Component.html +350 -0
  25. data/docs/api/Ovto/Runtime.html +315 -0
  26. data/docs/api/Ovto/State/MissingValue.html +135 -0
  27. data/docs/api/Ovto/State/UnknownKey.html +135 -0
  28. data/docs/api/Ovto/State.html +699 -0
  29. data/docs/api/Ovto/WiredActions.html +343 -0
  30. data/docs/api/Ovto.html +319 -0
  31. data/docs/api/_index.html +229 -0
  32. data/docs/api/actions.html +398 -0
  33. data/docs/api/app.html +411 -0
  34. data/docs/api/class_list.html +51 -0
  35. data/docs/api/component.html +469 -0
  36. data/docs/api/css/common.css +1 -0
  37. data/docs/api/css/full_list.css +58 -0
  38. data/docs/api/css/style.css +499 -0
  39. data/docs/api/file.README.html +162 -0
  40. data/docs/api/file_list.html +56 -0
  41. data/docs/api/frames.html +17 -0
  42. data/docs/api/index.html +162 -0
  43. data/docs/api/js/app.js +248 -0
  44. data/docs/api/js/full_list.js +216 -0
  45. data/docs/api/js/jquery.js +4 -0
  46. data/docs/api/method_list.html +243 -0
  47. data/docs/api/state.html +430 -0
  48. data/docs/api/top-level-namespace.html +110 -0
  49. data/docs/gitbook/fonts/fontawesome/FontAwesome.otf +0 -0
  50. data/docs/gitbook/fonts/fontawesome/fontawesome-webfont.eot +0 -0
  51. data/docs/gitbook/fonts/fontawesome/fontawesome-webfont.svg +685 -0
  52. data/docs/gitbook/fonts/fontawesome/fontawesome-webfont.ttf +0 -0
  53. data/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff +0 -0
  54. data/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 +0 -0
  55. data/docs/gitbook/gitbook-plugin-fontsettings/fontsettings.js +240 -0
  56. data/docs/gitbook/gitbook-plugin-fontsettings/website.css +291 -0
  57. data/docs/gitbook/gitbook-plugin-highlight/ebook.css +135 -0
  58. data/docs/gitbook/gitbook-plugin-highlight/website.css +434 -0
  59. data/docs/gitbook/gitbook-plugin-lunr/lunr.min.js +7 -0
  60. data/docs/gitbook/gitbook-plugin-lunr/search-lunr.js +59 -0
  61. data/docs/gitbook/gitbook-plugin-search/lunr.min.js +7 -0
  62. data/docs/gitbook/gitbook-plugin-search/search-engine.js +50 -0
  63. data/docs/gitbook/gitbook-plugin-search/search.css +35 -0
  64. data/docs/gitbook/gitbook-plugin-search/search.js +213 -0
  65. data/docs/gitbook/gitbook-plugin-sharing/buttons.js +90 -0
  66. data/docs/gitbook/gitbook.js +4 -0
  67. data/docs/gitbook/images/apple-touch-icon-precomposed-152.png +0 -0
  68. data/docs/gitbook/images/favicon.ico +0 -0
  69. data/docs/gitbook/style.css +9 -0
  70. data/docs/gitbook/theme.js +4 -0
  71. data/docs/guides/debugging.html +355 -0
  72. data/docs/guides/development.html +361 -0
  73. data/docs/guides/tutorial.html +571 -0
  74. data/docs/index.html +422 -0
  75. data/docs/screenshot.png +0 -0
  76. data/docs/search_index.json +1 -0
  77. data/example/sinatra/Gemfile +6 -0
  78. data/example/sinatra/Gemfile.lock +59 -0
  79. data/example/sinatra/README.md +21 -0
  80. data/example/sinatra/app.rb +18 -0
  81. data/example/sinatra/config.ru +30 -0
  82. data/example/sinatra/ovto/app.rb +171 -0
  83. data/example/sinatra/public/style.css +4 -0
  84. data/example/sinatra/public/todomvc-app-css_index.css +376 -0
  85. data/example/sinatra/public/todomvc-common_base.css +141 -0
  86. data/example/sinatra/views/index.erb +21 -0
  87. data/example/static/Gemfile +3 -0
  88. data/example/static/Gemfile.lock +30 -0
  89. data/example/static/README.md +10 -0
  90. data/example/static/Rakefile +4 -0
  91. data/example/static/app.js +24808 -0
  92. data/example/static/app.rb +43 -0
  93. data/example/static/index.html +11 -0
  94. data/lib/ovto/actions.rb +10 -0
  95. data/lib/ovto/app.rb +58 -0
  96. data/lib/ovto/component.rb +191 -0
  97. data/lib/ovto/fetch.rb +53 -0
  98. data/lib/ovto/runtime.rb +388 -0
  99. data/lib/ovto/state.rb +69 -0
  100. data/lib/ovto/version.rb +3 -0
  101. data/lib/ovto/wired_actions.rb +33 -0
  102. data/lib/ovto.rb +50 -0
  103. data/ovto.gemspec +22 -0
  104. data/screenshot.png +0 -0
  105. metadata +161 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3b5c09cd1d638ce54f7aea489477c059ac6935704ae284c5fdae49c7b45b7cdd
4
+ data.tar.gz: 2c83538298b032f336e035dc7ad48ea71114bb8f6ece4054accd9d3a593db5e6
5
+ SHA512:
6
+ metadata.gz: 316292a3ef746099b7c1eb5054d34830771b428122325972c91a5c1924123a544f5016e0eab4904120df56d16be5c70c50cb4c4dedd43a5e821eecd53c5aafbb
7
+ data.tar.gz: 478d66e8ce0c82f40b258c9490baa64b617b4d66f772931f7c1c5cbd52f2d0a70073d62c98ec0863954de6d6436725c4adad988d85d3ef98754fc00aef70f584
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ /.yardoc
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "opal-rspec"]
2
+ path = opal-rspec
3
+ url = git://github.com/opal/opal-rspec.git
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ ## v0.2.0 2018-11-01
2
+
3
+ New features
4
+
5
+ - First gem release!
6
+ - `Ovto.fetch`
7
+ - Calling another action from actions
8
+ - Allow `o "div#id.class"` or `o "div.class#id"`
9
+ - Sub component can access the app state by adding `state:` keyword to `render`
10
+ - `App#actions`, `App#setup`
11
+ - `State#==`
12
+ - `console.log`
13
+
14
+ Fixes
15
+
16
+ - Skip rendering if app state is not changed by an action
17
+ - `o "div.main", class: 'hovered'` should yield `<div class='main hovered'>`
18
+ - Cannot pass falsy value when rendering a child component
19
+
20
+ ## v0.1.0 2018-06-01
21
+
22
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+ gem "opal"
3
+ gem 'opal-rspec', path: './opal-rspec'
4
+ gem 'opal-sprockets', github: 'opal/opal-sprockets'
5
+ gem 'rake'
6
+ gem 'ovto', path: '.'
7
+ gem 'yard'
data/Gemfile.lock ADDED
@@ -0,0 +1,57 @@
1
+ GIT
2
+ remote: git://github.com/opal/opal-sprockets.git
3
+ revision: 8a8faeb1cfbc51cfa2e71ddb6cf219bd4a14f333
4
+ specs:
5
+ opal-sprockets (0.4.2.0.11.0.3.1)
6
+ opal (~> 0.11.0)
7
+ sprockets (~> 3.1)
8
+ tilt (>= 1.4)
9
+
10
+ PATH
11
+ remote: .
12
+ specs:
13
+ ovto (0.2.0)
14
+ opal (~> 0.11)
15
+
16
+ PATH
17
+ remote: opal-rspec
18
+ specs:
19
+ opal-rspec (0.7.0.dev)
20
+ opal (>= 0.10.0, < 0.12)
21
+ opal-sprockets
22
+
23
+ GEM
24
+ remote: https://rubygems.org/
25
+ specs:
26
+ ast (2.4.0)
27
+ concurrent-ruby (1.0.5)
28
+ hike (1.2.3)
29
+ opal (0.11.3)
30
+ ast (>= 2.3.0)
31
+ hike (~> 1.2)
32
+ parser (= 2.3.3.1)
33
+ sourcemap (~> 0.1.0)
34
+ parser (2.3.3.1)
35
+ ast (~> 2.2)
36
+ rack (2.0.5)
37
+ rake (12.3.1)
38
+ sourcemap (0.1.1)
39
+ sprockets (3.7.2)
40
+ concurrent-ruby (~> 1.0)
41
+ rack (> 1, < 3)
42
+ tilt (2.0.8)
43
+ yard (0.9.16)
44
+
45
+ PLATFORMS
46
+ ruby
47
+
48
+ DEPENDENCIES
49
+ opal
50
+ opal-rspec!
51
+ opal-sprockets!
52
+ ovto!
53
+ rake
54
+ yard
55
+
56
+ BUNDLED WITH
57
+ 1.16.1
data/LICENSE.txt ADDED
@@ -0,0 +1,44 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018-present Yutaka HARA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
23
+ ----
24
+ License of Hyperapp (in lib/ovto/runtime.rb):
25
+
26
+ Copyright © 2017-present [Jorge Bucaran](https://github.com/jorgebucaran)
27
+
28
+ Permission is hereby granted, free of charge, to any person obtaining a copy
29
+ of this software and associated documentation files (the "Software"), to deal
30
+ in the Software without restriction, including without limitation the rights
31
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32
+ copies of the Software, and to permit persons to whom the Software is
33
+ furnished to do so, subject to the following conditions:
34
+
35
+ The above copyright notice and this permission notice shall be included in all
36
+ copies or substantial portions of the Software.
37
+
38
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # Ovto
2
+
3
+ Client-side web framework for Opal, inspired by hyperapp
4
+
5
+ ## Documents
6
+
7
+ - [Book](https://yhara.github.io/ovto/)
8
+ - [yardoc](https://yhara.github.io/ovto/api/)
9
+
10
+ ## Example
11
+
12
+ ![screenshot](screenshot.png)
13
+
14
+ ```rb
15
+ require 'ovto'
16
+
17
+ class MyApp < Ovto::App
18
+ class State < Ovto::State
19
+ item :celsius, default: 0
20
+
21
+ def fahrenheit
22
+ (celsius * 9 / 5.0) + 32
23
+ end
24
+ end
25
+
26
+ class Actions < Ovto::Actions
27
+ def set_celsius(state:, value:)
28
+ return {celsius: value}
29
+ end
30
+
31
+ def set_fahrenheit(state:, value:)
32
+ new_celsius = (value - 32) * 5 / 9.0
33
+ return {celsius: new_celsius}
34
+ end
35
+ end
36
+
37
+ class View < Ovto::Component
38
+ def render(state:)
39
+ o 'div' do
40
+ o 'span', 'Celcius:'
41
+ o 'input', {
42
+ type: 'text',
43
+ onchange: ->(e){ actions.set_celsius(value: e.target.value.to_i) },
44
+ value: state.celsius
45
+ }
46
+ o 'span', 'Fahrenheit:'
47
+ o 'input', {
48
+ type: 'text',
49
+ onchange: ->(e){ actions.set_fahrenheit(value: e.target.value.to_i) },
50
+ value: state.fahrenheit
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ MyApp.run(id: 'ovto-view')
58
+ ```
59
+
60
+ See the [book](https://yhara.github.io/ovto/guides/tutorial.html) for details.
61
+
62
+ ## Setup
63
+
64
+ ### Static
65
+
66
+ [./example/static](./example/static) demonstrates how to convert Ovto app
67
+ into a .js file.
68
+
69
+ ### Ovto + Sinatra
70
+
71
+ [./example/sinatra](./example/sinatra) demonstrates how to serve Ovto app
72
+ from Sinatra.
73
+
74
+ ### Ovto + Rails
75
+
76
+ [yhara/vision](https://github.com/yhara/vision) is a working example of using Ovto with Rails 5.
77
+
78
+ ## Acknowledgements
79
+
80
+ - [hyperapp](https://github.com/hyperapp/hyperapp)
81
+
82
+ ## Contact
83
+
84
+ https://github.com/yhara/ovto/issues
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ require 'opal/rspec/rake_task'
2
+ Opal.append_path "./opal"
3
+ Opal.append_path "./spec"
4
+ Opal.use_gem "ovto"
5
+
6
+ Opal::RSpec::RakeTask.new(:default) do |server, task|
7
+ task.pattern = 'spec/**/*_spec.rb'
8
+ end
9
+
10
+ namespace :docs do
11
+ desc "build docs"
12
+ task :build do
13
+ cd "book" do
14
+ sh "gitbook build . ../docs"
15
+ end
16
+ sh "yardoc -o docs/api"
17
+ end
18
+
19
+ desc "start gitbook server"
20
+ task :serve do
21
+ cd "book" do
22
+ sh "gitbook serve"
23
+ end
24
+ end
25
+ end
26
+
27
+ namespace :release do
28
+ task :prepare_commit do
29
+ sh "git ci -am v#{Ovto::VERSION}"
30
+ sh "git tag v#{Ovto::VERSION}"
31
+ end
32
+
33
+ task :push_commit do
34
+ sh "git push origin master --tags"
35
+ end
36
+
37
+ task :push_gem do
38
+ sh "gem build ovto"
39
+ sh "gem push ovto-#{Ovto::VERSION}.gem"
40
+ end
41
+ end
data/book/README.md ADDED
@@ -0,0 +1 @@
1
+ book/../README.md
data/book/SUMMARY.md ADDED
@@ -0,0 +1,13 @@
1
+ # Summary
2
+
3
+ - [Introduction](README.md)
4
+ - [Getting Started](guides/tutorial.md)
5
+ - API
6
+ - [Ovto::App](api/app.md)
7
+ - [Ovto::State](api/state.md)
8
+ - [Ovto::Actions](api/actions.md)
9
+ - [Ovto::Component](api/component.md)
10
+ - [Ovto.fetch](api/fetch.md)
11
+ - Guides
12
+ - [Debugging](guides/debugging.md)
13
+ - [Development](guides/development.md)
@@ -0,0 +1,77 @@
1
+ # Ovto::Actions
2
+
3
+ Actions are the only way to change the state. Actions must be defined as methods of
4
+ the `Actions` class. Here is some more conventions:
5
+
6
+ - You must use keyword arguments
7
+ - You must provide `state:` keyword to receive the app state
8
+ - You must return state updates as a Hash. It will be merged into the app state.
9
+
10
+ Example:
11
+
12
+ ```rb
13
+ require 'opal'
14
+ require 'ovto'
15
+
16
+ class MyApp < Ovto::App
17
+ class State < Ovto::State
18
+ item :count, 0
19
+ end
20
+
21
+ class Actions < Ovto::Actions
22
+ def increment(state:, by:)
23
+ return {count: state.count + by}
24
+ end
25
+ end
26
+
27
+ class View < Ovto::Component
28
+ def render(state:)
29
+ o 'span', state.count
30
+ o 'button', onclick: ->{ actions.increment(by: 1) }
31
+ end
32
+ end
33
+ end
34
+
35
+ MyApp.run(id: 'ovto-view')
36
+ ```
37
+
38
+ ## Calling actions
39
+
40
+ Actions can be called from components via `actions` method. This is an instance of
41
+ `Ovto::WiredActions` and has methods of the same name as your `Actions` class.
42
+
43
+ o 'button', onclick: ->{ actions.increment(by: 1) }
44
+
45
+ Arguments are almost the same as the original but you don't need to provide `state`;
46
+ it is automatically passed by `Ovto::WiredActions` class. It also updates the app
47
+ state with the return value of the action, and schedules rendering the view.
48
+
49
+ ## Skipping state update
50
+
51
+ An action may return `nil` when no app state changes are needed.
52
+
53
+ Promises are also special values which does not cause state changes (see the next section).
54
+
55
+ ## Async actions
56
+
57
+ When calling server apis, you cannot tell how the app state will change until the server responds.
58
+ In such cases, you can call another action via `actions` to tell Ovto to reflect the api result to the app state.
59
+
60
+ Example:
61
+
62
+ ```rb
63
+ class Actions < Ovto::Actions
64
+ def fetch_tasks(state:)
65
+ Ovto.fetch('/tasks.json').then {|tasks_json|
66
+ actions.receive_tasks(tasks: tasks_json)
67
+ }.fail {|e|
68
+ console.log("failed:", e)
69
+ }
70
+ end
71
+
72
+ def receive_tasks(state:, tasks_json:)
73
+ tasks = tasks_json.map{|item| Task.new(**item)}
74
+ return {tasks: tasks}
75
+ end
76
+ end
77
+ ```
data/book/api/app.md ADDED
@@ -0,0 +1,86 @@
1
+ # Ovto::App
2
+
3
+ First of all, you need to define a subclass of `Ovto::App` and define `class State`,
4
+ `class Actions` and `class View` in it.
5
+
6
+ ## Example
7
+
8
+ This is a smallest Ovto app.
9
+
10
+ ```rb
11
+ require 'opal'
12
+ require 'ovto'
13
+
14
+ class MyApp < Ovto::App
15
+ class State < Ovto::State
16
+ end
17
+
18
+ class Actions < Ovto::Actions
19
+ end
20
+
21
+ class View < Ovto::Component
22
+ def render(state:)
23
+ o 'input', type: 'button', value: 'Hello'
24
+ end
25
+ end
26
+ end
27
+
28
+ MyApp.run(id: 'ovto-view')
29
+ ```
30
+
31
+ It renders a button and does nothing else. Let's have some fun:
32
+
33
+ ```rb
34
+ require 'opal'
35
+ require 'ovto'
36
+
37
+ class MyApp < Ovto::App
38
+ COLORS = ["red", "blue", "green"]
39
+
40
+ class State < Ovto::State
41
+ item :color_idx, default: 0
42
+ end
43
+
44
+ class Actions < Ovto::Actions
45
+ def update_color(state:)
46
+ new_idx = (state.color_idx + 1) % COLORS.length
47
+ return {color_idx: new_idx}
48
+ end
49
+ end
50
+
51
+ class View < Ovto::Component
52
+ def render(state:)
53
+ o 'input', {
54
+ type: 'button',
55
+ value: 'Hello',
56
+ style: {background: COLORS[state.color_idx]},
57
+ onclick: ->{ actions.update_color },
58
+ }
59
+ end
60
+ end
61
+ end
62
+
63
+ MyApp.run(id: 'ovto-view')
64
+ ```
65
+
66
+ Here we added `color_idx` to app state and `update_color` action to change it.
67
+ The button is updated to have the color indicated by `color_idx` and
68
+ now has `onclick` event handler which executes the action.
69
+
70
+ ## Calling actions on startup
71
+
72
+ To invoke certain actions on app startup, define `MyApp#setup` and use `MyApp#actions`.
73
+
74
+ Example:
75
+
76
+ ```rb
77
+ class MyApp < Ovto::App
78
+ def setup
79
+ actions.fetch_data()
80
+ end
81
+
82
+ ...
83
+ end
84
+
85
+ MyApp.run(id: 'ovto-view')
86
+ ```
@@ -0,0 +1,175 @@
1
+ # Ovto::Component
2
+
3
+ Your app must have a `View` class, a subclass of `Ovto::Component`.
4
+
5
+ ## 'render' method
6
+
7
+ `render` is the only method you need to define in the `View` class.
8
+ It must take the global app state as a keyword argument `state:`.
9
+
10
+ ```rb
11
+ class View < Ovto::Component
12
+ def render(state:)
13
+ o 'div' do
14
+ o 'h1', 'Your todos'
15
+ o 'ul' do
16
+ state.todos.each do |todo|
17
+ o 'li', todo.title
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ ```
24
+
25
+ ### MoreThanOneNode error
26
+
27
+ If you missed the surrounding 'div' tag, Ovto raises an `MoreThanOneNode` error. `render` must create a single DOM node.
28
+
29
+ ```rb
30
+ def render(state:)
31
+ o 'h1', 'Your todos'
32
+ o 'ul' do
33
+ state.todos.each do |todo|
34
+ o 'li', todo.title
35
+ end
36
+ end
37
+ end
38
+
39
+ #=> $MoreThanOneNode {name: "MoreThanOneNode", message: "MyApp::View#render must generate a single DOM node. Please wrap the tags with a 'div' or something.", stack: "MoreThanOneNode: MyApp::View#render must generate …opbox/proj/ovto/example/tutorial/app.js:22887:18)"}
40
+ ```
41
+
42
+ ## The 'o' method
43
+
44
+ <a name='the-o-method' />
45
+
46
+ `Ovto::Component#o` describes your app's view. For example:
47
+
48
+ ```rb
49
+ o 'div'
50
+ #=> <div></div>
51
+
52
+ o 'div', 'Hello.'
53
+ #=> <div>Hello.</div>
54
+ ```
55
+
56
+ You can pass attributes with a Hash.
57
+
58
+ ```rb
59
+ o 'div', class: 'main', 'Hello.'
60
+ #=> <div class='main'>Hello.</div>
61
+ ```
62
+
63
+ There are shorthand notations for classes and ids.
64
+
65
+ ```rb
66
+ o 'div.main'
67
+ #=> <div class='main'>Hello.</div>
68
+
69
+ o 'div#main'
70
+ #=> <div id='main'>Hello.</div>
71
+ ```
72
+
73
+ You can also give a block to specify its content.
74
+
75
+ ```rb
76
+ o 'div' do
77
+ 'Hello.'
78
+ end
79
+ #=> <div>Hello.</div>
80
+
81
+ o 'div' do
82
+ o 'h1', 'Hello.'
83
+ end
84
+ #=> <div><h1>Hello.</h1></div>
85
+ ```
86
+
87
+ ### Special attribute: `style`
88
+
89
+ <a name='special-attributes' />
90
+
91
+ There are some special keys for the attributes Hash. `style:` key takes a hash as
92
+ its value and specifies styles of the tag.
93
+
94
+ ```rb
95
+ o 'div', style: {color: 'red'}, 'Hello.'
96
+ #=> <div style='color: red;'>Hello.</div>
97
+ ```
98
+
99
+ ### Special attribute: `onxx`
100
+
101
+ An attribute starts with `"on"` specifies an event handler.
102
+
103
+ For example:
104
+
105
+ ```rb
106
+ o 'input', {
107
+ type: 'button',
108
+ onclick: ->(e){ p e.target.value },
109
+ value: 'Hello.'
110
+ }
111
+ ```
112
+
113
+ The argument `e` is an instance of Opal::Native and wraps the JavaScript event object.
114
+ You can get the input value with `e.target.value` here.
115
+
116
+ #### Lifecycle events
117
+
118
+ There are special events `oncreate`, `onupdate`, `onremove`, `ondestroy`.
119
+
120
+ https://github.com/hyperapp/hyperapp#lifecycle-events
121
+
122
+ ### Special attribute: `key`
123
+
124
+ https://github.com/hyperapp/hyperapp#keys
125
+
126
+ ## Sub components
127
+
128
+ `o` can take another component class to render.
129
+
130
+ ```rb
131
+ # Sub component
132
+ class TodoList < Ovto::Component
133
+ def render(todos:)
134
+ o 'ul' do
135
+ todos.each do |todo|
136
+ o 'li', todo.title
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ # Main View class
143
+ class View < Ovto::Component
144
+ def render(state:)
145
+ o 'div' do
146
+ o 'h1', 'Your todos'
147
+ o TodoList, todos: state.todos
148
+ end
149
+ end
150
+ end
151
+ ```
152
+
153
+ For sub components, the `state:` keyword of `render` method is optional.
154
+
155
+ ## Text node
156
+
157
+ Sometimes you may want to create a text node.
158
+
159
+ ```rb
160
+ #=> <div>Age: <span class='age'>12</a></div>
161
+ # ~~~~~
162
+ # |
163
+ # +--Raw text (not enclosed by an inner tag)
164
+ ```
165
+
166
+ `o` generates a text node when `'text'` is specified as tag name. The above
167
+ HTML could be described like this.
168
+
169
+ ```rb
170
+ o 'div' do
171
+ o 'text', 'Age:'
172
+ o 'span', '12'
173
+ end
174
+ ```
175
+
data/book/api/fetch.md ADDED
@@ -0,0 +1,42 @@
1
+ # Ovto.fetch
2
+
3
+ Ovto provides wrapper of [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch), for convenience of calling typical server-side APIs (eg. those generated by `rails scaffold` command.)
4
+
5
+ `Ovto.fetch` returns Opal's Promise object that calls the API with the specified parameters.
6
+
7
+ ## Examples
8
+
9
+ GET
10
+
11
+ ```rb
12
+ Ovto.fetch('/api/tasks'){|json_data|
13
+ p json_data
14
+ }.fail{|e| # Network error, 404 Not Found, JSON parse error, etc.
15
+ p e
16
+ }
17
+ ```
18
+
19
+ POST
20
+
21
+ ```rb
22
+ Ovto.fetch('/api/new_task', 'POST', {title: "do something"}){|json_data|
23
+ p json_data
24
+ }.fail{|e| # Network error, 404 Not Found, JSON parse error, etc.
25
+ p e
26
+ }
27
+
28
+ ```
29
+
30
+ PUT
31
+
32
+ ```rb
33
+ Ovto.fetch('/api/tasks/1', 'PUT', {title: "do something"}){|json_data|
34
+ p json_data
35
+ }.fail{|e| # Network error, 404 Not Found, JSON parse error, etc.
36
+ p e
37
+ }
38
+ ```
39
+
40
+ ## CSRF tokens
41
+
42
+ You don't need to care about CSRF tokens if the server is a Rails app because `Ovto.fetch` automatically send `X-CSRF-Token` header if the page contains a meta tag like `<meta name='csrf-token' content='....' />`.