ovto 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile.lock +15 -16
  4. data/Rakefile +2 -9
  5. data/book/SUMMARY.md +15 -11
  6. data/book/book.toml +10 -0
  7. data/docs/.nojekyll +1 -0
  8. data/docs/404.html +189 -0
  9. data/docs/FontAwesome/css/font-awesome.css +4 -0
  10. data/docs/FontAwesome/fonts/FontAwesome.ttf +0 -0
  11. data/docs/FontAwesome/fonts/fontawesome-webfont.eot +0 -0
  12. data/docs/FontAwesome/fonts/fontawesome-webfont.svg +2671 -0
  13. data/docs/{gitbook/fonts/fontawesome → FontAwesome/fonts}/fontawesome-webfont.ttf +0 -0
  14. data/docs/FontAwesome/fonts/fontawesome-webfont.woff +0 -0
  15. data/docs/FontAwesome/fonts/fontawesome-webfont.woff2 +0 -0
  16. data/docs/api/Array.html +4 -4
  17. data/docs/api/Hash.html +4 -4
  18. data/docs/api/MightyInspect.html +238 -0
  19. data/docs/api/Ovto/Actions.html +4 -4
  20. data/docs/api/Ovto/App.html +4 -4
  21. data/docs/api/Ovto/Component/MoreThanOneNode.html +5 -5
  22. data/docs/api/Ovto/Component.html +4 -4
  23. data/docs/api/Ovto/Middleware/Actions.html +4 -4
  24. data/docs/api/Ovto/Middleware/Base.html +5 -5
  25. data/docs/api/Ovto/Middleware/Component.html +5 -5
  26. data/docs/api/Ovto/Middleware.html +8 -8
  27. data/docs/api/Ovto/PureComponent/StateIsNotAvailable.html +4 -4
  28. data/docs/api/Ovto/PureComponent.html +4 -4
  29. data/docs/api/Ovto/Runtime.html +4 -4
  30. data/docs/api/Ovto/State/MissingValue.html +4 -4
  31. data/docs/api/Ovto/State/UnknownStateKey.html +4 -4
  32. data/docs/api/Ovto/State.html +12 -12
  33. data/docs/api/Ovto/WiredActionSet.html +4 -4
  34. data/docs/api/Ovto/WiredActions.html +4 -4
  35. data/docs/api/Ovto.html +8 -8
  36. data/docs/api/_index.html +5 -5
  37. data/docs/api/actions.html +215 -426
  38. data/docs/api/app.html +226 -432
  39. data/docs/api/component.html +268 -480
  40. data/docs/api/fetch.html +188 -393
  41. data/docs/api/file.README.html +4 -4
  42. data/docs/api/frames.html +1 -1
  43. data/docs/api/index.html +4 -4
  44. data/docs/api/middleware.html +249 -460
  45. data/docs/api/pure_component.html +186 -398
  46. data/docs/api/state.html +226 -438
  47. data/docs/api/top-level-namespace.html +4 -4
  48. data/docs/ayu-highlight.css +78 -0
  49. data/docs/book.js +688 -0
  50. data/docs/book.toml +10 -0
  51. data/docs/clipboard.min.js +7 -0
  52. data/docs/css/chrome.css +545 -0
  53. data/docs/css/general.css +203 -0
  54. data/docs/css/print.css +54 -0
  55. data/docs/css/variables.css +255 -0
  56. data/docs/elasticlunr.min.js +10 -0
  57. data/docs/favicon.png +0 -0
  58. data/docs/favicon.svg +22 -0
  59. data/docs/fonts/OPEN-SANS-LICENSE.txt +202 -0
  60. data/docs/fonts/SOURCE-CODE-PRO-LICENSE.txt +93 -0
  61. data/docs/fonts/fonts.css +100 -0
  62. data/docs/fonts/open-sans-v17-all-charsets-300.woff2 +0 -0
  63. data/docs/fonts/open-sans-v17-all-charsets-300italic.woff2 +0 -0
  64. data/docs/fonts/open-sans-v17-all-charsets-600.woff2 +0 -0
  65. data/docs/fonts/open-sans-v17-all-charsets-600italic.woff2 +0 -0
  66. data/docs/fonts/open-sans-v17-all-charsets-700.woff2 +0 -0
  67. data/docs/fonts/open-sans-v17-all-charsets-700italic.woff2 +0 -0
  68. data/docs/fonts/open-sans-v17-all-charsets-800.woff2 +0 -0
  69. data/docs/fonts/open-sans-v17-all-charsets-800italic.woff2 +0 -0
  70. data/docs/fonts/open-sans-v17-all-charsets-italic.woff2 +0 -0
  71. data/docs/fonts/open-sans-v17-all-charsets-regular.woff2 +0 -0
  72. data/docs/fonts/source-code-pro-v11-all-charsets-500.woff2 +0 -0
  73. data/docs/guides/debugging.html +184 -390
  74. data/docs/guides/development.html +171 -383
  75. data/docs/guides/install.html +206 -409
  76. data/docs/guides/tutorial.html +309 -525
  77. data/docs/highlight.css +82 -0
  78. data/docs/highlight.js +6 -0
  79. data/docs/index.html +390 -391
  80. data/docs/mark.min.js +7 -0
  81. data/docs/print.html +958 -0
  82. data/docs/searcher.js +483 -0
  83. data/docs/searchindex.js +1 -0
  84. data/docs/searchindex.json +1 -0
  85. data/docs/tomorrow-night.css +102 -0
  86. data/examples/sinatra/Gemfile.lock +25 -25
  87. data/examples/static/Gemfile.lock +8 -8
  88. data/lib/ovto/component.rb +2 -3
  89. data/lib/ovto/version.rb +1 -1
  90. metadata +47 -26
  91. data/docs/gitbook/fonts/fontawesome/FontAwesome.otf +0 -0
  92. data/docs/gitbook/fonts/fontawesome/fontawesome-webfont.eot +0 -0
  93. data/docs/gitbook/fonts/fontawesome/fontawesome-webfont.svg +0 -685
  94. data/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff +0 -0
  95. data/docs/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 +0 -0
  96. data/docs/gitbook/gitbook-plugin-fontsettings/fontsettings.js +0 -240
  97. data/docs/gitbook/gitbook-plugin-fontsettings/website.css +0 -291
  98. data/docs/gitbook/gitbook-plugin-highlight/ebook.css +0 -135
  99. data/docs/gitbook/gitbook-plugin-highlight/website.css +0 -434
  100. data/docs/gitbook/gitbook-plugin-lunr/lunr.min.js +0 -7
  101. data/docs/gitbook/gitbook-plugin-lunr/search-lunr.js +0 -59
  102. data/docs/gitbook/gitbook-plugin-search/lunr.min.js +0 -7
  103. data/docs/gitbook/gitbook-plugin-search/search-engine.js +0 -50
  104. data/docs/gitbook/gitbook-plugin-search/search.css +0 -35
  105. data/docs/gitbook/gitbook-plugin-search/search.js +0 -213
  106. data/docs/gitbook/gitbook-plugin-sharing/buttons.js +0 -90
  107. data/docs/gitbook/gitbook.js +0 -4
  108. data/docs/gitbook/images/apple-touch-icon-precomposed-152.png +0 -0
  109. data/docs/gitbook/images/favicon.ico +0 -0
  110. data/docs/gitbook/style.css +0 -9
  111. data/docs/gitbook/theme.js +0 -4
  112. data/docs/search_index.json +0 -1
data/docs/print.html ADDED
@@ -0,0 +1,958 @@
1
+ <!DOCTYPE HTML>
2
+ <html lang="en" class="sidebar-visible no-js light">
3
+ <head>
4
+ <!-- Book generated using mdBook -->
5
+ <meta charset="UTF-8">
6
+ <title>Ovto Reference Manual</title>
7
+ <meta name="robots" content="noindex" />
8
+
9
+
10
+ <!-- Custom HTML head -->
11
+
12
+ <meta name="description" content="">
13
+ <meta name="viewport" content="width=device-width, initial-scale=1">
14
+ <meta name="theme-color" content="#ffffff" />
15
+
16
+ <link rel="icon" href="favicon.svg">
17
+ <link rel="shortcut icon" href="favicon.png">
18
+ <link rel="stylesheet" href="css/variables.css">
19
+ <link rel="stylesheet" href="css/general.css">
20
+ <link rel="stylesheet" href="css/chrome.css">
21
+ <link rel="stylesheet" href="css/print.css" media="print">
22
+
23
+ <!-- Fonts -->
24
+ <link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
25
+ <link rel="stylesheet" href="fonts/fonts.css">
26
+
27
+ <!-- Highlight.js Stylesheets -->
28
+ <link rel="stylesheet" href="highlight.css">
29
+ <link rel="stylesheet" href="tomorrow-night.css">
30
+ <link rel="stylesheet" href="ayu-highlight.css">
31
+
32
+ <!-- Custom theme stylesheets -->
33
+
34
+ </head>
35
+ <body>
36
+ <div id="body-container">
37
+ <!-- Provide site root to javascript -->
38
+ <script>
39
+ var path_to_root = "";
40
+ var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
41
+ </script>
42
+
43
+ <!-- Work around some values being stored in localStorage wrapped in quotes -->
44
+ <script>
45
+ try {
46
+ var theme = localStorage.getItem('mdbook-theme');
47
+ var sidebar = localStorage.getItem('mdbook-sidebar');
48
+
49
+ if (theme.startsWith('"') && theme.endsWith('"')) {
50
+ localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
51
+ }
52
+
53
+ if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
54
+ localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
55
+ }
56
+ } catch (e) { }
57
+ </script>
58
+
59
+ <!-- Set the theme before any content is loaded, prevents flash -->
60
+ <script>
61
+ var theme;
62
+ try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
63
+ if (theme === null || theme === undefined) { theme = default_theme; }
64
+ var html = document.querySelector('html');
65
+ html.classList.remove('no-js')
66
+ html.classList.remove('light')
67
+ html.classList.add(theme);
68
+ html.classList.add('js');
69
+ </script>
70
+
71
+ <!-- Hide / unhide sidebar before it is displayed -->
72
+ <script>
73
+ var html = document.querySelector('html');
74
+ var sidebar = null;
75
+ if (document.body.clientWidth >= 1080) {
76
+ try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
77
+ sidebar = sidebar || 'visible';
78
+ } else {
79
+ sidebar = 'hidden';
80
+ }
81
+ html.classList.remove('sidebar-visible');
82
+ html.classList.add("sidebar-" + sidebar);
83
+ </script>
84
+
85
+ <nav id="sidebar" class="sidebar" aria-label="Table of contents">
86
+ <div class="sidebar-scrollbox">
87
+ <ol class="chapter"><li class="chapter-item expanded "><a href="guides/tutorial.html"><strong aria-hidden="true">1.</strong> Getting Started</a></li><li class="chapter-item expanded "><a href="guides/install.html"><strong aria-hidden="true">2.</strong> Install</a></li><li class="chapter-item expanded affix "><li class="part-title">API</li><li class="chapter-item expanded "><a href="api/app.html"><strong aria-hidden="true">3.</strong> Ovto::App</a></li><li class="chapter-item expanded "><a href="api/state.html"><strong aria-hidden="true">4.</strong> Ovto::State</a></li><li class="chapter-item expanded "><a href="api/actions.html"><strong aria-hidden="true">5.</strong> Ovto::Actions</a></li><li class="chapter-item expanded "><a href="api/component.html"><strong aria-hidden="true">6.</strong> Ovto::Component</a></li><li class="chapter-item expanded "><a href="api/pure_component.html"><strong aria-hidden="true">7.</strong> Ovto::PureComponent</a></li><li class="chapter-item expanded "><a href="api/middleware.html"><strong aria-hidden="true">8.</strong> Ovto::Middleware</a></li><li class="chapter-item expanded "><a href="api/fetch.html"><strong aria-hidden="true">9.</strong> Ovto.fetch</a></li><li class="chapter-item expanded affix "><li class="part-title">Guides</li><li class="chapter-item expanded "><a href="guides/debugging.html"><strong aria-hidden="true">10.</strong> Debugging</a></li><li class="chapter-item expanded "><a href="guides/development.html"><strong aria-hidden="true">11.</strong> Development</a></li></ol>
88
+ </div>
89
+ <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
90
+ </nav>
91
+
92
+ <div id="page-wrapper" class="page-wrapper">
93
+
94
+ <div class="page">
95
+ <div id="menu-bar-hover-placeholder"></div>
96
+ <div id="menu-bar" class="menu-bar sticky bordered">
97
+ <div class="left-buttons">
98
+ <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
99
+ <i class="fa fa-bars"></i>
100
+ </button>
101
+ <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
102
+ <i class="fa fa-paint-brush"></i>
103
+ </button>
104
+ <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
105
+ <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
106
+ <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
107
+ <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
108
+ <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
109
+ <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
110
+ </ul>
111
+ <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
112
+ <i class="fa fa-search"></i>
113
+ </button>
114
+ </div>
115
+
116
+ <h1 class="menu-title">Ovto Reference Manual</h1>
117
+
118
+ <div class="right-buttons">
119
+ <a href="print.html" title="Print this book" aria-label="Print this book">
120
+ <i id="print-button" class="fa fa-print"></i>
121
+ </a>
122
+
123
+ </div>
124
+ </div>
125
+
126
+ <div id="search-wrapper" class="hidden">
127
+ <form id="searchbar-outer" class="searchbar-outer">
128
+ <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
129
+ </form>
130
+ <div id="searchresults-outer" class="searchresults-outer hidden">
131
+ <div id="searchresults-header" class="searchresults-header"></div>
132
+ <ul id="searchresults">
133
+ </ul>
134
+ </div>
135
+ </div>
136
+
137
+ <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
138
+ <script>
139
+ document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
140
+ document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
141
+ Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
142
+ link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
143
+ });
144
+ </script>
145
+
146
+ <div id="content" class="content">
147
+ <main>
148
+ <h1 id="getting-started"><a class="header" href="#getting-started">Getting Started</a></h1>
149
+ <p>This is a tutorial of making an Ovto app. We create a static app (.html + .js) here,
150
+ but you can embed Ovto apps into a Rails or Sinatra app (See <code>./examples/*</code>).</p>
151
+ <p>This is the final Ruby code.</p>
152
+ <pre><code class="language-rb">require 'ovto'
153
+
154
+ class MyApp &lt; Ovto::App
155
+ class State &lt; Ovto::State
156
+ item :celsius, default: 0
157
+
158
+ def fahrenheit
159
+ (celsius * 9 / 5.0) + 32
160
+ end
161
+ end
162
+
163
+ class Actions &lt; Ovto::Actions
164
+ def set_celsius(value:)
165
+ return {celsius: value}
166
+ end
167
+
168
+ def set_fahrenheit(value:)
169
+ new_celsius = (value - 32) * 5 / 9.0
170
+ return {celsius: new_celsius}
171
+ end
172
+ end
173
+
174
+ class MainComponent &lt; Ovto::Component
175
+ def render
176
+ o 'div' do
177
+ o 'span', 'Celcius:'
178
+ o 'input', {
179
+ type: 'text',
180
+ onchange: -&gt;(e){ actions.set_celsius(value: e.target.value.to_i) },
181
+ value: state.celsius
182
+ }
183
+ o 'span', 'Fahrenheit:'
184
+ o 'input', {
185
+ type: 'text',
186
+ onchange: -&gt;(e){ actions.set_fahrenheit(value: e.target.value.to_i) },
187
+ value: state.fahrenheit
188
+ }
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ MyApp.run(id: 'ovto')
195
+ </code></pre>
196
+ <p>Let's take a look step-by-step.</p>
197
+ <h2 id="prerequisites"><a class="header" href="#prerequisites">Prerequisites</a></h2>
198
+ <ul>
199
+ <li>Ruby</li>
200
+ <li>Bundler (<code>gem install bundler</code>)</li>
201
+ </ul>
202
+ <h2 id="setup"><a class="header" href="#setup">Setup</a></h2>
203
+ <p>Make a Gemfile:</p>
204
+ <pre><code class="language-rb">source &quot;https://rubygems.org&quot;
205
+ gem &quot;ovto&quot;, github: 'yhara/ovto' # Use git master because ovto gem is not released yet
206
+ gem 'rake'
207
+ </code></pre>
208
+ <p>Run <code>bundle install</code>.</p>
209
+ <h2 id="html"><a class="header" href="#html">HTML</a></h2>
210
+ <p>Make an index.html:</p>
211
+ <pre><code class="language-html">&lt;!doctype html&gt;
212
+ &lt;html&gt;
213
+ &lt;head&gt;
214
+ &lt;meta charset=&quot;utf-8&quot;&gt;
215
+ &lt;script type='text/javascript' src='app.js'&gt;&lt;/script&gt;
216
+ &lt;/head&gt;
217
+ &lt;body&gt;
218
+ &lt;div id='ovto'&gt;&lt;/div&gt;
219
+ &lt;div id='ovto-debug'&gt;&lt;/div&gt;
220
+ &lt;/body&gt;
221
+ &lt;/html&gt;
222
+ </code></pre>
223
+ <h2 id="write-code"><a class="header" href="#write-code">Write code</a></h2>
224
+ <p>app.rb:</p>
225
+ <pre><code class="language-rb">require 'ovto'
226
+
227
+ class MyApp &lt; Ovto::App
228
+ class State &lt; Ovto::State
229
+ end
230
+
231
+ class Actions &lt; Ovto::Actions
232
+ end
233
+
234
+ class MainComponent &lt; Ovto::Component
235
+ def render # Don't miss the `:`. This is not a typo but
236
+ o 'div' do # a &quot;mandatory keyword argument&quot;.
237
+ o 'h1', &quot;HELLO&quot; # All of the Ovto methods take keyword arguments.
238
+ end
239
+ end
240
+ end
241
+ end
242
+
243
+ MyApp.run(id: 'ovto')
244
+ </code></pre>
245
+ <ul>
246
+ <li>The name <code>MyApp</code> is arbitrary.</li>
247
+ <li>The id <code>ovto</code> corresponds to the <code>div</code> tag in <code>index.html</code>.</li>
248
+ </ul>
249
+ <h2 id="compile"><a class="header" href="#compile">Compile</a></h2>
250
+ <p>Generate app.js from app.rb.</p>
251
+ <pre><code>$ bundle exec opal -c -g ovto app.rb &gt; app.js
252
+ </code></pre>
253
+ <p>(Compile will fail if there is a syntax error in your <code>app.rb</code>.)</p>
254
+ <p>Now you can run your app by opening <code>index.html</code> in your browser.</p>
255
+ <h2 id="trouble-shooting"><a class="header" href="#trouble-shooting">Trouble shooting</a></h2>
256
+ <p>If you see HELLO, the setup is done. Otherwise, check the developer console
257
+ and you should see some error messages there.</p>
258
+ <p>For example if you misspelled <code>class State</code> to <code>class Stat</code>, you will see:</p>
259
+ <pre><code>app.js:5022 Uncaught $NameError {name: &quot;State&quot;, message: &quot;uninitialized constant MyApp::State&quot;, stack: &quot;State: uninitialized constant MyApp::State&quot;}
260
+ </code></pre>
261
+ <p>because an Ovto app must have a <code>State</code> class in its namespace.</p>
262
+ <h2 id="tips-auto-compile"><a class="header" href="#tips-auto-compile">(Tips: auto-compile)</a></h2>
263
+ <p>If you get tired to run <code>bundle exec opal</code> manually, try <code>ifchanged</code> gem:</p>
264
+ <ol>
265
+ <li>Add <code>gem &quot;ifchanged&quot;</code> to Gemfile</li>
266
+ <li><code>bundle install</code></li>
267
+ <li><code>bundle exec ifchanged ./app.rb --do 'bundle exec opal -c -g ovto app.rb &gt; app.js'</code></li>
268
+ </ol>
269
+ <p>Now you just edit and save <code>app.rb</code> and it runs <code>opal -c</code> for you.</p>
270
+ <h2 id="add-some-state"><a class="header" href="#add-some-state">Add some state</a></h2>
271
+ <p>In this tutorial, we make an app that convers Celsius and Fahrenheit degrees to
272
+ each other. First, add an item to <code>MyApp::State</code>.</p>
273
+ <pre><code class="language-rb"> class State &lt; Ovto::State
274
+ item :celsius, default: 0
275
+ end
276
+ </code></pre>
277
+ <p>Now an item <code>celsius</code> is added to the global app state. Its value is <code>0</code> when
278
+ the app starts. You can read this value by <code>state.celsius</code>. Let's display the
279
+ value with <code>MyApp::MainComponent</code>.</p>
280
+ <pre><code class="language-rb"> class MainComponent &lt; Ovto::Component
281
+ def render
282
+ o 'div' do
283
+ o 'span', 'Celcius:'
284
+ o 'input', type: 'text', value: state.celsius
285
+ end
286
+ end
287
+ end
288
+ </code></pre>
289
+ <p>Now you should see <code>Celsius: [0 ]</code> in the browser.</p>
290
+ <h2 id="add-a-method-to-state"><a class="header" href="#add-a-method-to-state">Add a method to State</a></h2>
291
+ <p>Next, we want to know what degree is it in Fahrenheit. Let's add a method to
292
+ convert.</p>
293
+ <pre><code class="language-rb"> class State &lt; Ovto::State
294
+ item :celsius, default: 0
295
+
296
+ def fahrenheit
297
+ (celsius * 9 / 5.0) + 32
298
+ end
299
+ end
300
+ </code></pre>
301
+ <p>Now you can know the value by <code>state.fahrenheit</code>. Update <code>MainComponent</code> to show the value too.</p>
302
+ <pre><code class="language-rb"> class MainComponent &lt; Ovto::Component
303
+ def render
304
+ o 'div' do
305
+ o 'span', 'Celcius:'
306
+ o 'input', type: 'text', value: state.celsius
307
+ o 'span', 'Fahrenheit:'
308
+ o 'input', type: 'text', value: state.fahrenheit
309
+ end
310
+ end
311
+ end
312
+ </code></pre>
313
+ <h2 id="add-an-action"><a class="header" href="#add-an-action">Add an action</a></h2>
314
+ <p>Now we know 0 degrees Celsius is 32 degrees Fahrenheit. But how about 10 degrees or
315
+ 100 degrees Celsius? Let's update the app to we can specify a Celsius value.</p>
316
+ <p>You may think that you can change the value with <code>state.celsius = 100</code>, but this is
317
+ prohibited. In Ovto, you can only modify app state with Actions.</p>
318
+ <p>Our first action looks like this. An action is a method defined in <code>MyApp::Actions</code>.
319
+ It takes an old state (and its own parameters) and returns a Hash that describes
320
+ the updates to the state. This return value is <code>merge</code>d into the global app state.</p>
321
+ <pre><code class="language-rb"> class Actions &lt; Ovto::Actions
322
+ def set_celsius(value:)
323
+ return {celsius: value}
324
+ end
325
+ end
326
+ </code></pre>
327
+ <p>This action can be called by <code>actions.set_celsius</code> from MainComponent. Replace the
328
+ first input tag with this:</p>
329
+ <pre><code class="language-rb"> o 'input', {
330
+ type: 'text',
331
+ onchange: -&gt;(e){ actions.set_celsius(value: e.target.value.to_i) },
332
+ value: state.celsius
333
+ }
334
+ </code></pre>
335
+ <p><code>onchange:</code> is a special attribute that takes an event handler as its value.
336
+ The argument <code>e</code> is an instance of <code>Opal::Native</code> and wraps the event object of
337
+ JavaScript. In this case you can get the input string by <code>e.target.value</code>.</p>
338
+ <p>Now reload your browser and input <code>100</code> to the left input box. Next, press Tab key
339
+ (or click somewhere in the page) to commit the value. Then you should see <code>212</code>
340
+ in the right input box. 100 degrees Celsius is 212 degrees Fahrenheit!</p>
341
+ <h2 id="what-has-happend"><a class="header" href="#what-has-happend">What has happend</a></h2>
342
+ <p>In case you are curious, here is what happens when you give 100 to the input box.</p>
343
+ <ol>
344
+ <li>JavaScript's <code>onchange</code> event is executed.</li>
345
+ <li>Ovto calls the event handler.</li>
346
+ <li>It calls <code>actions.set_celsius</code>. <code>actions</code> is an instance of <code>Ovto::WiredActions</code>.
347
+ It is a proxy to the <code>MyApp::Actions</code>. It has the same methods as those in
348
+ <code>MyApp::Actions</code> but does some more:</li>
349
+ </ol>
350
+ <ul>
351
+ <li>It passes <code>state</code> to the user-defined action.</li>
352
+ <li>It merges the result to the global app state.</li>
353
+ <li>It schedules re-rendering the view to represent the new state.</li>
354
+ </ul>
355
+ <h2 id="reverse-conversion"><a class="header" href="#reverse-conversion">Reverse conversion</a></h2>
356
+ <p>It is easy to update the app to support Fahrenheit-to-Celsius conversion.
357
+ The second input should be updated to:</p>
358
+ <pre><code class="language-rb"> o 'input', {
359
+ type: 'text',
360
+ onchange: -&gt;(e){ actions.set_fahrenheit(value: e.target.value.to_i) },
361
+ value: state.fahrenheit
362
+ }
363
+ </code></pre>
364
+ <p>Then add an action <code>set_fahrenheit</code> to <code>MyApp::Actions</code>. This action convers the
365
+ Fahrenheit degree into Celsius and set it to the global state.</p>
366
+ <pre><code class="language-rb"> def set_fahrenheit(value:)
367
+ new_celsius = (value - 32) * 5 / 9.0
368
+ return {celsius: new_celsius}
369
+ end
370
+ </code></pre>
371
+ <p>Now your app should react to the change of the Fahrenheit value too. </p>
372
+ <div style="break-before: page; page-break-before: always;"></div><h1 id="install"><a class="header" href="#install">Install</a></h1>
373
+ <h2 id="use-ovto-with-static-files"><a class="header" href="#use-ovto-with-static-files">Use Ovto with static files</a></h2>
374
+ <pre><code>$ gem i ovto
375
+ $ ovto new myapp --static
376
+ </code></pre>
377
+ <h2 id="use-ovto-with-sinatra"><a class="header" href="#use-ovto-with-sinatra">Use Ovto with Sinatra</a></h2>
378
+ <pre><code>$ gem i ovto
379
+ $ ovto new myapp --sinatra
380
+ </code></pre>
381
+ <h2 id="install-ovto-into-rails-apps"><a class="header" href="#install-ovto-into-rails-apps">Install Ovto into Rails apps</a></h2>
382
+ <p>Edit <code>Gemfile</code></p>
383
+ <pre><code class="language-rb">gem 'opal-rails'
384
+ gem 'ovto'
385
+ </code></pre>
386
+ <p>Run <code>bundle install</code></p>
387
+ <p>Remove <code>app/assets/javascripts/application.js</code></p>
388
+ <p>Create <code>app/assets/javascripts/application.js.rb</code></p>
389
+ <pre><code class="language-rb">require 'opal'
390
+ require 'rails-ujs'
391
+ require 'activestorage'
392
+ require 'turbolinks'
393
+ require_tree '.'
394
+ </code></pre>
395
+ <p>Create <code>app/assets/javascripts/foo.js.rb</code> (file name is arbitrary)</p>
396
+ <pre><code class="language-rb">require 'ovto'
397
+
398
+ class Foo &lt; Ovto::App
399
+ class State &lt; Ovto::State
400
+ end
401
+
402
+ class Actions &lt; Ovto::Actions
403
+ end
404
+
405
+ class MainComponent &lt; Ovto::Component
406
+ def render(state:)
407
+ o 'h1', &quot;HELLO&quot;
408
+ end
409
+ end
410
+ end
411
+ </code></pre>
412
+ <p>Edit <code>app/views/&lt;some controller/&lt;some view&gt;.html.erb</code></p>
413
+ <pre><code>&lt;div id='foo-app'&gt;&lt;/div&gt;
414
+ &lt;%= opal_tag do %&gt;
415
+ Foo.run(id: 'foo-app')
416
+ &lt;% end %&gt;
417
+ </code></pre>
418
+ <p>This should render <code>HELLO</code> in the browser.</p>
419
+ <p>You also need to edit config/environments/production.rb like this before deploy it to production.</p>
420
+ <pre><code class="language-rb"> #config.assets.js_compressor = :uglifier
421
+ config.assets.js_compressor = Uglifier.new(harmony: true)
422
+ </code></pre>
423
+ <div style="break-before: page; page-break-before: always;"></div><h1 id="ovtoapp"><a class="header" href="#ovtoapp">Ovto::App</a></h1>
424
+ <p>First of all, you need to define a subclass of <code>Ovto::App</code> and define <code>class State</code>,
425
+ <code>class Actions</code> and <code>class MainComponent</code> in it.</p>
426
+ <h2 id="example"><a class="header" href="#example">Example</a></h2>
427
+ <p>This is a smallest Ovto app.</p>
428
+ <pre><code class="language-rb">require 'opal'
429
+ require 'ovto'
430
+
431
+ class MyApp &lt; Ovto::App
432
+ class State &lt; Ovto::State
433
+ end
434
+
435
+ class Actions &lt; Ovto::Actions
436
+ end
437
+
438
+ class MainComponent &lt; Ovto::Component
439
+ def render
440
+ o 'input', type: 'button', value: 'Hello'
441
+ end
442
+ end
443
+ end
444
+
445
+ MyApp.run(id: 'ovto')
446
+ </code></pre>
447
+ <p>It renders a button and does nothing else. Let's have some fun:</p>
448
+ <pre><code class="language-rb">require 'opal'
449
+ require 'ovto'
450
+
451
+ class MyApp &lt; Ovto::App
452
+ COLORS = [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;]
453
+
454
+ class State &lt; Ovto::State
455
+ item :color_idx, default: 0
456
+ end
457
+
458
+ class Actions &lt; Ovto::Actions
459
+ def update_color
460
+ new_idx = (state.color_idx + 1) % COLORS.length
461
+ return {color_idx: new_idx}
462
+ end
463
+ end
464
+
465
+ class MainComponent &lt; Ovto::Component
466
+ def render
467
+ o 'input', {
468
+ type: 'button',
469
+ value: 'Hello',
470
+ style: {background: COLORS[state.color_idx]},
471
+ onclick: -&gt;{ actions.update_color },
472
+ }
473
+ end
474
+ end
475
+ end
476
+
477
+ MyApp.run(id: 'ovto')
478
+ </code></pre>
479
+ <p>Here we added <code>color_idx</code> to app state and <code>update_color</code> action to change it.
480
+ The button is updated to have the color indicated by <code>color_idx</code> and
481
+ now has <code>onclick</code> event handler which executes the action.</p>
482
+ <h2 id="calling-actions-on-startup"><a class="header" href="#calling-actions-on-startup">Calling actions on startup</a></h2>
483
+ <p>To invoke certain actions on app startup, define <code>MyApp#setup</code> and use <code>MyApp#actions</code>.</p>
484
+ <p>Example:</p>
485
+ <pre><code class="language-rb">class MyApp &lt; Ovto::App
486
+ def setup
487
+ actions.fetch_data()
488
+ end
489
+
490
+ ...
491
+ end
492
+
493
+ MyApp.run(id: 'ovto')
494
+ </code></pre>
495
+ <div style="break-before: page; page-break-before: always;"></div><h1 id="ovtostate"><a class="header" href="#ovtostate">Ovto::State</a></h1>
496
+ <p><code>Ovto::State</code> is like a hash, but members are accessible with name rather than <code>[]</code>.</p>
497
+ <h2 id="example-1"><a class="header" href="#example-1">Example</a></h2>
498
+ <pre><code class="language-rb">class State &lt; Ovto::State
499
+ item :foo
500
+ item :bar
501
+ end
502
+
503
+ state = State.new(foo: 1, bar: 2)
504
+ state.foo #=&gt; 1
505
+ state.bar #=&gt; 2
506
+ </code></pre>
507
+ <h2 id="default-value"><a class="header" href="#default-value">Default value</a></h2>
508
+ <pre><code class="language-rb">class State &lt; Ovto::State
509
+ item :foo, default: 1
510
+ item :bar, default: 2
511
+ end
512
+
513
+ state = State.new
514
+ state.foo #=&gt; 1
515
+ state.bar #=&gt; 2
516
+ </code></pre>
517
+ <h2 id="immutable"><a class="header" href="#immutable">Immutable</a></h2>
518
+ <p>State objects are immutable. i.e. you cannot update value of a key. Instead, use <code>State#merge</code>.</p>
519
+ <pre><code class="language-rb">state = State.new(foo: 1, bar: 2)
520
+ new_state = state.merge(bar: 3)
521
+ new_state.foo #=&gt; 1
522
+ new_state.bar #=&gt; 3
523
+ </code></pre>
524
+ <h2 id="nesting-state"><a class="header" href="#nesting-state">Nesting state</a></h2>
525
+ <p>For practical apps, you can nest State like this.</p>
526
+ <pre><code class="language-rb">class Book &lt; Ovto::State
527
+ item :title
528
+ item :author
529
+ end
530
+
531
+ class State &lt; Ovto::State
532
+ item :books, []
533
+ end
534
+
535
+ book = Book.new('Hello world', 'taro')
536
+ state = State.new(books: [book])
537
+ </code></pre>
538
+ <h2 id="defining-instance-methods-of-state"><a class="header" href="#defining-instance-methods-of-state">Defining instance methods of state</a></h2>
539
+ <p>You can define instance methods of state.</p>
540
+ <pre><code class="language-rb">class Book &lt; Ovto::State
541
+ item :title
542
+ item :author
543
+
544
+ def to_text
545
+ &quot;#{self.title} (#{self.author})&quot;
546
+ end
547
+ end
548
+
549
+ book = Book.new('Hello world', 'taro')
550
+ book.to_text #=&gt; &quot;Hello world (taro)&quot;
551
+ </code></pre>
552
+ <h2 id="defining-class-methods-of-state"><a class="header" href="#defining-class-methods-of-state">Defining class methods of state</a></h2>
553
+ <p>Ovto does not have a class like <code>StateList</code>. Just use Array to represent a list of state.</p>
554
+ <p>You can define class methods to manipulate a list of state.</p>
555
+ <pre><code class="language-rb">class Book &lt; Ovto::State
556
+ item :title
557
+ item :author
558
+
559
+ def self.of_author(books, author)
560
+ books.select{|x| x.author == author}
561
+ end
562
+ end
563
+
564
+ # Example
565
+ taro_books = Book.of_author(books, &quot;taro&quot;)
566
+ </code></pre>
567
+ <div style="break-before: page; page-break-before: always;"></div><h1 id="ovtoactions"><a class="header" href="#ovtoactions">Ovto::Actions</a></h1>
568
+ <p>Actions are the only way to change the state. Actions must be defined as methods of
569
+ the <code>Actions</code> class. Here is some more conventions:</p>
570
+ <ul>
571
+ <li>You must use keyword arguments</li>
572
+ <li>You must return state updates as a Hash. It will be merged into the app state.</li>
573
+ <li>You can get the current state by <code>state</code> method</li>
574
+ </ul>
575
+ <p>Example:</p>
576
+ <pre><code class="language-rb">require 'opal'
577
+ require 'ovto'
578
+
579
+ class MyApp &lt; Ovto::App
580
+ class State &lt; Ovto::State
581
+ item :count, default: 0
582
+ end
583
+
584
+ class Actions &lt; Ovto::Actions
585
+ def increment(by:)
586
+ return {count: state.count + by}
587
+ end
588
+ end
589
+
590
+ class MainComponent &lt; Ovto::Component
591
+ def render
592
+ o 'span', state.count
593
+ o 'button', onclick: -&gt;{ actions.increment(by: 1) }
594
+ end
595
+ end
596
+ end
597
+
598
+ MyApp.run(id: 'ovto')
599
+ </code></pre>
600
+ <h2 id="calling-actions"><a class="header" href="#calling-actions">Calling actions</a></h2>
601
+ <p>Actions can be called from components via <code>actions</code> method. This is an instance of
602
+ <code>Ovto::WiredActions</code> and has methods of the same name as your <code>Actions</code> class.</p>
603
+ <pre><code> o 'button', onclick: -&gt;{ actions.increment(by: 1) }
604
+ </code></pre>
605
+ <p>Arguments are almost the same as the original but you don't need to provide <code>state</code>;
606
+ it is automatically passed by <code>Ovto::WiredActions</code> class. It also updates the app
607
+ state with the return value of the action, and schedules rendering the view.</p>
608
+ <h2 id="skipping-state-update"><a class="header" href="#skipping-state-update">Skipping state update</a></h2>
609
+ <p>An action may return <code>nil</code> when no app state changes are needed.</p>
610
+ <p>Promises are also special values which does not cause state changes (see the next section).</p>
611
+ <h2 id="async-actions"><a class="header" href="#async-actions">Async actions</a></h2>
612
+ <p>When calling server apis, you cannot tell how the app state will change until the server responds.
613
+ In such cases, you can call another action via <code>actions</code> to tell Ovto to reflect the api result to the app state.</p>
614
+ <p>Example:</p>
615
+ <pre><code class="language-rb"> class Actions &lt; Ovto::Actions
616
+ def fetch_tasks
617
+ Ovto.fetch('/tasks.json').then {|tasks_json|
618
+ actions.receive_tasks(tasks: tasks_json)
619
+ }.fail {|e|
620
+ console.log(&quot;failed:&quot;, e)
621
+ }
622
+ end
623
+
624
+ def receive_tasks(tasks_json:)
625
+ tasks = tasks_json.map{|item| Task.new(**item)}
626
+ return {tasks: tasks}
627
+ end
628
+ end
629
+ </code></pre>
630
+ <div style="break-before: page; page-break-before: always;"></div><h1 id="ovtocomponent"><a class="header" href="#ovtocomponent">Ovto::Component</a></h1>
631
+ <p>An Ovto app must have <code>MainComponent</code> class, a subclass of <code>Ovto::Component</code>.</p>
632
+ <h2 id="render-method"><a class="header" href="#render-method">'render' method</a></h2>
633
+ <p><code>render</code> is the only method you need to define in the <code>MainComponent</code> class.
634
+ You can get the global app state by calling <code>state</code> method.</p>
635
+ <pre><code class="language-rb"> class MainComponent &lt; Ovto::Component
636
+ def render
637
+ o 'div' do
638
+ o 'h1', 'Your todos'
639
+ o 'ul' do
640
+ state.todos.each do |todo|
641
+ o 'li', todo.title
642
+ end
643
+ end
644
+ end
645
+ end
646
+ end
647
+ </code></pre>
648
+ <h3 id="morethanonenode-error"><a class="header" href="#morethanonenode-error">MoreThanOneNode error</a></h3>
649
+ <p>If you missed the surrounding 'div' tag, Ovto raises an <code>MoreThanOneNode</code> error. <code>render</code> must create a single DOM node.</p>
650
+ <pre><code class="language-rb"> def render
651
+ o 'h1', 'Your todos'
652
+ o 'ul' do
653
+ state.todos.each do |todo|
654
+ o 'li', todo.title
655
+ end
656
+ end
657
+ end
658
+
659
+ #=&gt; $MoreThanOneNode {name: &quot;MoreThanOneNode&quot;, ...}
660
+ </code></pre>
661
+ <h2 id="the-o-method"><a class="header" href="#the-o-method">The 'o' method</a></h2>
662
+ <a name='the-o-method' />
663
+ <p><code>Ovto::Component#o</code> describes your app's view. For example:</p>
664
+ <pre><code class="language-rb">o 'div'
665
+ #=&gt; &lt;div&gt;&lt;/div&gt;
666
+
667
+ o 'div', 'Hello.'
668
+ #=&gt; &lt;div&gt;Hello.&lt;/div&gt;
669
+ </code></pre>
670
+ <p>You can pass attributes with a Hash.</p>
671
+ <pre><code class="language-rb">o 'div', class: 'main', 'Hello.'
672
+ #=&gt; &lt;div class='main'&gt;Hello.&lt;/div&gt;
673
+ </code></pre>
674
+ <p>There are shorthand notations for classes and ids.</p>
675
+ <pre><code class="language-rb">o 'div.main'
676
+ #=&gt; &lt;div class='main'&gt;Hello.&lt;/div&gt;
677
+
678
+ o 'div#main'
679
+ #=&gt; &lt;div id='main'&gt;Hello.&lt;/div&gt;
680
+ </code></pre>
681
+ <p>You can also give a block to specify its content.</p>
682
+ <pre><code class="language-rb">o 'div' do
683
+ 'Hello.'
684
+ end
685
+ #=&gt; &lt;div&gt;Hello.&lt;/div&gt;
686
+
687
+ o 'div' do
688
+ o 'h1', 'Hello.'
689
+ end
690
+ #=&gt; &lt;div&gt;&lt;h1&gt;Hello.&lt;/h1&gt;&lt;/div&gt;
691
+ </code></pre>
692
+ <h3 id="special-attribute-style"><a class="header" href="#special-attribute-style">Special attribute: <code>style</code></a></h3>
693
+ <a name='special-attributes' />
694
+ <p>There are some special keys for the attributes Hash. <code>style:</code> key takes a hash as
695
+ its value and specifies styles of the tag.</p>
696
+ <pre><code class="language-rb">o 'div', style: {color: 'red'}, 'Hello.'
697
+ #=&gt; &lt;div style='color: red;'&gt;Hello.&lt;/div&gt;
698
+ </code></pre>
699
+ <h3 id="special-attribute-onxx"><a class="header" href="#special-attribute-onxx">Special attribute: <code>onxx</code></a></h3>
700
+ <p>An attribute starts with <code>&quot;on&quot;</code> specifies an event handler.</p>
701
+ <p>For example:</p>
702
+ <pre><code class="language-rb">o 'input', {
703
+ type: 'button',
704
+ onclick: -&gt;(e){ p e.target.value },
705
+ value: 'Hello.'
706
+ }
707
+ </code></pre>
708
+ <p>The argument <code>e</code> is an instance of Opal::Native and wraps the JavaScript event object.
709
+ You can get the input value with <code>e.target.value</code> here.</p>
710
+ <h4 id="lifecycle-events"><a class="header" href="#lifecycle-events">Lifecycle events</a></h4>
711
+ <p>There are special events <code>oncreate</code>, <code>onupdate</code>, <code>onremove</code>, <code>ondestroy</code>.</p>
712
+ <p>https://github.com/hyperapp/hyperapp#lifecycle-events</p>
713
+ <h3 id="special-attribute-key"><a class="header" href="#special-attribute-key">Special attribute: <code>key</code></a></h3>
714
+ <p>https://github.com/hyperapp/hyperapp#keys</p>
715
+ <h2 id="sub-components"><a class="header" href="#sub-components">Sub components</a></h2>
716
+ <p><code>o</code> can take another component class to render.</p>
717
+ <pre><code class="language-rb"> # Sub component
718
+ class TodoList &lt; Ovto::Component
719
+ def render(todos:)
720
+ o 'ul' do
721
+ todos.each do |todo|
722
+ o 'li', todo.title
723
+ end
724
+ end
725
+ end
726
+ end
727
+
728
+ # Main component
729
+ class MainComponent &lt; Ovto::Component
730
+ def render
731
+ o 'div' do
732
+ o 'h1', 'Your todos'
733
+ o TodoList, todos: state.todos
734
+ end
735
+ end
736
+ end
737
+ </code></pre>
738
+ <h2 id="text-node"><a class="header" href="#text-node">Text node</a></h2>
739
+ <p>Sometimes you may want to create a text node.</p>
740
+ <pre><code class="language-rb">#=&gt; &lt;div&gt;Age: &lt;span class='age'&gt;12&lt;/a&gt;&lt;/div&gt;
741
+ # ~~~~~
742
+ # |
743
+ # +--Raw text (not enclosed by an inner tag)
744
+ </code></pre>
745
+ <p><code>o</code> generates a text node when <code>'text'</code> is specified as tag name. The above
746
+ HTML could be described like this.</p>
747
+ <pre><code class="language-rb">o 'div' do
748
+ o 'text', 'Age:'
749
+ o 'span', '12'
750
+ end
751
+ </code></pre>
752
+ <div style="break-before: page; page-break-before: always;"></div><h1 id="ovtopurecomponent"><a class="header" href="#ovtopurecomponent">Ovto::PureComponent</a></h1>
753
+ <p>It almost the same as <code>Ovto::Component</code>, but it caches the <code>render</code> method calling with arguments of the method.</p>
754
+ <h2 id="when-to-use-purecomponent"><a class="header" href="#when-to-use-purecomponent">When to use PureComponent?</a></h2>
755
+ <p>Use it when your app is slow and need more speed.</p>
756
+ <h2 id="cache-strategy"><a class="header" href="#cache-strategy">Cache strategy</a></h2>
757
+ <p>It compares <code>render</code> method arguments and the previous arguments.</p>
758
+ <pre><code class="language-rb">def render
759
+ o 'div' do
760
+ o Pure, foo: state.foo
761
+ o NotPure bar: state.bar
762
+ end
763
+ end
764
+ </code></pre>
765
+ <p>In this case, <code>NotPure</code> component's render method is called even if <code>state.foo</code> is changed.
766
+ Whereas <code>Pure</code> component's render method is called only if <code>state.foo</code> is changed.</p>
767
+ <h2 id="state"><a class="header" href="#state">State</a></h2>
768
+ <p><code>state</code> method is not available in <code>PureComponent</code>, because <code>PureComponent</code> does not treat state as the cache key.
769
+ If you'd like to use state in <code>PureComponent</code>, pass the state from the parent component.</p>
770
+ <div style="break-before: page; page-break-before: always;"></div><h1 id="ovtomiddleware"><a class="header" href="#ovtomiddleware">Ovto::Middleware</a></h1>
771
+ <p>When you are making a big app with Ovto, you may want to extract
772
+ certain parts which are independent from the app. Ovto::Middleware is
773
+ for such cases.</p>
774
+ <ul>
775
+ <li>A middleware has its own namespace of state and actions
776
+ <ul>
777
+ <li>that is, you don't need to add prefixes to the names of states and actions of a middleware.</li>
778
+ </ul>
779
+ </li>
780
+ </ul>
781
+ <h2 id="example-2"><a class="header" href="#example-2">Example</a></h2>
782
+ <pre><code class="language-rb"># 1. Middleware name must be valid as a method name in Ruby
783
+ class MyMiddleware &lt; Ovto::Middleware(&quot;my_middleware&quot;)
784
+ def setup
785
+ # Called on app startup
786
+ end
787
+
788
+ # 2. Make a subclass of MyMiddleware's State
789
+ class State &lt; MyMiddleware::State
790
+ item :count, default: 0
791
+ end
792
+
793
+ # 3. Make a subclass of MyMiddleware's Actions
794
+ class Actions &lt; MyMiddleware::Actions
795
+ def increment(by:)
796
+ return {count: state.count + by}
797
+ end
798
+ end
799
+
800
+ # 4. Make a subclass of MyMiddleware's Component
801
+ class SomeComponent &lt; MyMiddleware::Component
802
+ def render
803
+ o 'div' do
804
+ o 'span', state.count
805
+ o 'button', onclick: -&gt;{ actions.increment(by: 1) }
806
+ end
807
+ end
808
+ end
809
+ end
810
+
811
+ class MyApp &lt; Ovto::App
812
+ # 5. Declare middlewares to use
813
+ use MyMiddleware
814
+
815
+ class State &lt; Ovto::State; end
816
+ class Actions &lt; Ovto::Actions; end
817
+
818
+ class MainComponent &lt; Ovto::Component
819
+ def render
820
+ o 'div.counter' do
821
+ o MyMiddleware::SomeComponent
822
+ end
823
+ end
824
+ end
825
+ end
826
+ </code></pre>
827
+ <h2 id="advanced"><a class="header" href="#advanced">Advanced</a></h2>
828
+ <h3 id="getting-middlware-state-from-app"><a class="header" href="#getting-middlware-state-from-app">Getting middlware state from app</a></h3>
829
+ <pre><code class="language-rb">class MyApp &lt; Ovto::App
830
+ def MainComponent &lt; Ovto::Component
831
+ def render
832
+ o 'span', state._middlewares.middleware1.some_state
833
+ end
834
+ end
835
+ end
836
+ </code></pre>
837
+ <h3 id="calling-middlware-action-from-app"><a class="header" href="#calling-middlware-action-from-app">Calling middlware action from app</a></h3>
838
+ <pre><code class="language-rb">class MyApp &lt; Ovto::App
839
+ # From actions
840
+ def Actions &lt; Ovto::Actions
841
+ def some_action
842
+ actions.middleware1.some_action()
843
+ end
844
+ end
845
+
846
+ # From component
847
+ def MainComponent &lt; Ovto::Component
848
+ def render
849
+ o 'button', onclick: -&gt;{ actions.middleware1.some_action() }
850
+ end
851
+ end
852
+ end
853
+ </code></pre>
854
+ <h3 id="using-a-middleware-from-another-middleware"><a class="header" href="#using-a-middleware-from-another-middleware">Using a middleware from another middleware</a></h3>
855
+ <pre><code class="language-rb">class Middleware1 &lt; Ovto::Middleware(&quot;middleware1&quot;)
856
+ use Middleware2
857
+ end
858
+ </code></pre>
859
+ <div style="break-before: page; page-break-before: always;"></div><h1 id="ovtofetch"><a class="header" href="#ovtofetch">Ovto.fetch</a></h1>
860
+ <p>Ovto provides wrapper of <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch">Fetch API</a>, for convenience of calling typical server-side APIs (eg. those generated by <code>rails scaffold</code> command.)</p>
861
+ <p><code>Ovto.fetch</code> returns Opal's Promise object that calls the API with the specified parameters.</p>
862
+ <h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
863
+ <p>GET</p>
864
+ <pre><code class="language-rb">Ovto.fetch('/api/tasks').then{|json_data|
865
+ p json_data
866
+ }.fail{|e| # Network error, 404 Not Found, JSON parse error, etc.
867
+ p e
868
+ }
869
+ </code></pre>
870
+ <p>POST</p>
871
+ <pre><code class="language-rb">Ovto.fetch('/api/new_task', 'POST', {title: &quot;do something&quot;}).then{|json_data|
872
+ p json_data
873
+ }.fail{|e| # Network error, 404 Not Found, JSON parse error, etc.
874
+ p e
875
+ }
876
+
877
+ </code></pre>
878
+ <p>PUT</p>
879
+ <pre><code class="language-rb">Ovto.fetch('/api/tasks/1', 'PUT', {title: &quot;do something&quot;}).then{|json_data|
880
+ p json_data
881
+ }.fail{|e| # Network error, 404 Not Found, JSON parse error, etc.
882
+ p e
883
+ }
884
+ </code></pre>
885
+ <h2 id="csrf-tokens"><a class="header" href="#csrf-tokens">CSRF tokens</a></h2>
886
+ <p>You don't need to care about CSRF tokens if the server is a Rails app because <code>Ovto.fetch</code> automatically send <code>X-CSRF-Token</code> header if the page contains a meta tag like <code>&lt;meta name='csrf-token' content='....' /&gt;</code>.</p>
887
+ <div style="break-before: page; page-break-before: always;"></div><h1 id="debugging-ovto-app"><a class="header" href="#debugging-ovto-app">Debugging Ovto app</a></h1>
888
+ <h2 id="consolelog"><a class="header" href="#consolelog">console.log</a></h2>
889
+ <p>In an Ovto app, you can print any object to developer console by <code>console.log</code>
890
+ like in JavaScript. </p>
891
+ <pre><code class="language-rb">console.log(state: State.new)
892
+ </code></pre>
893
+ <p>This is mostly equal to <code>p state: State.new</code> but <code>console.log</code> supports
894
+ JavaScript objects too.</p>
895
+ <p>(Note: this is not an official feature of Opal. You can do this setup by this:)</p>
896
+ <pre><code class="language-rb"> require 'console'; def console; $console; end
897
+ </code></pre>
898
+ <h2 id="ovto-debug"><a class="header" href="#ovto-debug">ovto-debug</a></h2>
899
+ <p>If the page has a tag with <code>id='ovto-debug'</code>, exception is shown in the tag.</p>
900
+ <h2 id="ovtodebug_trace"><a class="header" href="#ovtodebug_trace">Ovto.debug_trace</a></h2>
901
+ <p>If <code>Ovto.debug_trace</code> is set to <code>true</code>, some diagnostic messages are shown in the browser console.</p>
902
+ <pre><code class="language-rb">Ovto.debug_trace = true
903
+ MyApp.run(id: 'ovto')
904
+ </code></pre>
905
+ <div style="break-before: page; page-break-before: always;"></div><h1 id="development-notes"><a class="header" href="#development-notes">Development notes</a></h1>
906
+ <h2 id="how-to-run-unit-test"><a class="header" href="#how-to-run-unit-test">How to run unit test</a></h2>
907
+ <ol>
908
+ <li>git clone</li>
909
+ <li>bundle install</li>
910
+ <li>bundle exec rake</li>
911
+ </ol>
912
+ <h2 id="how-to-rebuild-docs"><a class="header" href="#how-to-rebuild-docs">How to rebuild docs</a></h2>
913
+ <p><code>bundle exec doc:build</code></p>
914
+
915
+ </main>
916
+
917
+ <nav class="nav-wrapper" aria-label="Page navigation">
918
+ <!-- Mobile navigation buttons -->
919
+
920
+
921
+ <div style="clear: both"></div>
922
+ </nav>
923
+ </div>
924
+ </div>
925
+
926
+ <nav class="nav-wide-wrapper" aria-label="Page navigation">
927
+
928
+ </nav>
929
+
930
+ </div>
931
+
932
+
933
+
934
+
935
+ <script>
936
+ window.playground_copyable = true;
937
+ </script>
938
+
939
+
940
+ <script src="elasticlunr.min.js"></script>
941
+ <script src="mark.min.js"></script>
942
+ <script src="searcher.js"></script>
943
+
944
+ <script src="clipboard.min.js"></script>
945
+ <script src="highlight.js"></script>
946
+ <script src="book.js"></script>
947
+
948
+ <!-- Custom JS scripts -->
949
+
950
+ <script>
951
+ window.addEventListener('load', function() {
952
+ window.setTimeout(window.print, 100);
953
+ });
954
+ </script>
955
+
956
+ </div>
957
+ </body>
958
+ </html>