Deploying to gh-pages from - 6f66eceb12 🚀

This commit is contained in:
jorgebucaran 2020-01-31 10:06:49 +00:00
parent 870fe4a948
commit cabadfe3b5
51 changed files with 1952 additions and 0 deletions

1
CNAME Normal file
View File

@ -0,0 +1 @@
hyperapp.dev

BIN
FiraCode-Bold.9341457d.woff Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
FiraCode-VF.2a9bbcf8.woff Normal file

Binary file not shown.

BIN
FiraCode-VF.fa7346fc.woff2 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

34
README.a5fa9974.html Normal file
View File

@ -0,0 +1,34 @@
<h1 id="hyperapp-npm"><a href="https://hyperapp.dev">Hyperapp</a> <a href="https://github.com/jorgebucaran/hyperapp/releases/latest"><img src="https://img.shields.io/npm/v/hyperapp.svg?label=&color=0080FF" alt="npm"></a></h1><blockquote>
<p>The tiny framework for building web interfaces.</p>
</blockquote><ul>
<li><strong>Do more with less</strong>—We have minimized the concepts you need to learn to be productive. Views, actions, effects, and subscriptions are all pretty easy to get to grips with and work together seamlessly.</li>
<li><strong>Write what, not how</strong>—With a declarative syntax that&#39;s easy to read and natural to write, Hyperapp is your tool of choice to develop purely functional, feature-rich, browser-based applications.</li>
<li><strong>Hypercharged</strong>—Hyperapp is a modern VDOM engine, state management solution, and application design pattern all-in-one. Once you learn to use it, there&#39;ll be no end to what you can do.</li>
</ul><p>To learn more, go to <a href="https://hyperapp.dev">https://hyperapp.dev</a> for documentation, guides, and examples.</p><h2 id="quickstart">Quickstart</h2><p>Install Hyperapp with npm or Yarn:</p><pre><code class="language-console">npm i hyperapp</code></pre><p>Then with a module bundler like <a href="https://parceljs.org">Parcel</a> or <a href="https://webpack.js.org">Webpack</a> import it in your application and get right down to business.</p><pre><code class="language-js">import { h, app } from &quot;hyperapp&quot;</code></pre><p>Don&#39;t want to set up a build step? Import Hyperapp in a <code>&lt;script&gt;</code> tag as a module. Don&#39;t worry; modules are supported in all evergreen, self-updating desktop, and mobile browsers.</p><pre><code class="language-html">&lt;script type=&quot;module&quot;&gt;
import { h, app } from &quot;https://unpkg.com/hyperapp&quot;
&lt;/script&gt;</code></pre><p>Here&#39;s the first example to get you started: a counter that can go up or down. You can try it online <a href="https://codesandbox.io/s/hyperapp-playground-fwjlo">here</a>.</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;script type=&quot;module&quot;&gt;
import { h, app } from &quot;https://unpkg.com/hyperapp&quot;
app({
init: 0,
view: state =&gt;
h(&quot;main&quot;, {}, [
h(&quot;h1&quot;, {}, state),
h(&quot;button&quot;, { onClick: state =&gt; state - 1 }, &quot;-&quot;),
h(&quot;button&quot;, { onClick: state =&gt; state + 1 }, &quot;+&quot;)
]),
node: document.getElementById(&quot;app&quot;)
})
&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;main id=&quot;app&quot;&gt;&lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p>The app starts off with <code>init</code> as the initial state. Our code doesn&#39;t explicitly maintain any state. Instead, we define actions to transform it and a view to visualize it. The view returns a plain object representation of the DOM known as a virtual DOM, and Hyperapp updates the real DOM to match it whenever the state changes.</p><p>Now it&#39;s your turn! Experiment with the code a bit. Spend some time thinking about how the view reacts to changes in the state. Can you add a button that resets the counter back to zero? Or how about multiple counters?</p><h2 id="help-im-stuck">Help, I&#39;m stuck!</h2><p>We love to talk JavaScript and Hyperapp. If you&#39;ve hit a stumbling block, hop on the <a href="https://hyperappjs.herokuapp.com">Hyperapp Slack</a> or drop by <a href="https://spectrum.chat/hyperapp">Spectrum</a> to get support, and if you don&#39;t receive an answer, or if you remain stuck, please file an issue, and we&#39;ll try to help you out.</p><p>Is anything wrong, unclear, missing? Help us <a href="https://github.com/jorgebucaran/hyperapp/fork">improve this page</a>.</p><h2 id="stay-in-the-loop">Stay in the loop</h2><ul>
<li><a href="https://twitter.com/hyperappjs">Twitter</a></li>
<li><a href="https://github.com/jorgebucaran/awesome-hyperapp">Awesome</a></li>
<li><a href="https://www.reddit.com/r/hyperapp">/r/hyperapp</a></li>
</ul><h2 id="license">License</h2><p><a href="/__/node_modules/hyperapp/LICENSE.html">MIT</a></p>

1
__/node_modules/hyperapp/LICENSE.html generated vendored Normal file
View File

@ -0,0 +1 @@
<p>Copyright © Jorge Bucaran &lt;<a href="https://jorgebucaran.com">https://jorgebucaran.com</a>&gt;</p><p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the &#39;Software&#39;), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p><p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p><p>THE SOFTWARE IS PROVIDED &#39;AS IS&#39;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>

4
_redirects Normal file
View File

@ -0,0 +1,4 @@
# Netlify redirects file
# Redirect 404s to index.html (404 should be handled by client)
/* /index.html 200

34
api.4a87837f.html Normal file
View File

@ -0,0 +1,34 @@
<h1 id="api">api</h1><blockquote>
<p>The tiny framework for building web interfaces.</p>
</blockquote><ul>
<li><strong>Do more with less</strong>—We have minimized the concepts you need to learn to be productive. Views, actions, effects, and subscriptions are all pretty easy to get to grips with and work together seamlessly.</li>
<li><strong>Write what, not how</strong>—With a declarative syntax that&#39;s easy to read and natural to write, Hyperapp is your tool of choice to develop purely functional, feature-rich, browser-based applications.</li>
<li><strong>Hypercharged</strong>—Hyperapp is a modern VDOM engine, state management solution, and application design pattern all-in-one. Once you learn to use it, there&#39;ll be no end to what you can do.</li>
</ul><p>To learn more, go to <a href="https://hyperapp.dev">https://hyperapp.dev</a> for documentation, guides, and examples.</p><h2 id="quickstart">Quickstart</h2><p>Install Hyperapp with npm or Yarn:</p><pre><code class="language-console">npm i hyperapp</code></pre><p>Then with a module bundler like <a href="https://parceljs.org">Parcel</a> or <a href="https://webpack.js.org">Webpack</a> import it in your application and get right down to business.</p><pre><code class="language-js">import { h, app } from &quot;hyperapp&quot;</code></pre><p>Don&#39;t want to set up a build step? Import Hyperapp in a <code>&lt;script&gt;</code> tag as a module. Don&#39;t worry; modules are supported in all evergreen, self-updating desktop, and mobile browsers.</p><pre><code class="language-html">&lt;script type=&quot;module&quot;&gt;
import { h, app } from &quot;https://unpkg.com/hyperapp&quot;
&lt;/script&gt;</code></pre><p>Here&#39;s the first example to get you started: a counter that can go up or down. You can try it online <a href="https://codesandbox.io/s/hyperapp-playground-fwjlo">here</a>.</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;script type=&quot;module&quot;&gt;
import { h, app } from &quot;https://unpkg.com/hyperapp&quot;
app({
init: 0,
view: state =&gt;
h(&quot;main&quot;, {}, [
h(&quot;h1&quot;, {}, state),
h(&quot;button&quot;, { onClick: state =&gt; state - 1 }, &quot;-&quot;),
h(&quot;button&quot;, { onClick: state =&gt; state + 1 }, &quot;+&quot;)
]),
node: document.getElementById(&quot;app&quot;)
})
&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;main id=&quot;app&quot;&gt;&lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p>The app starts off with <code>init</code> as the initial state. Our code doesn&#39;t explicitly maintain any state. Instead, we define actions to transform it and a view to visualize it. The view returns a plain object representation of the DOM known as a virtual DOM, and Hyperapp updates the real DOM to match it whenever the state changes.</p><p>Now it&#39;s your turn! Experiment with the code a bit. Spend some time thinking about how the view reacts to changes in the state. Can you add a button that resets the counter back to zero? Or how about multiple counters?</p><h2 id="help-im-stuck">Help, I&#39;m stuck!</h2><p>We love to talk JavaScript and Hyperapp. If you&#39;ve hit a stumbling block, hop on the <a href="https://hyperappjs.herokuapp.com">Hyperapp Slack</a> or drop by <a href="https://spectrum.chat/hyperapp">Spectrum</a> to get support, and if you don&#39;t receive an answer, or if you remain stuck, please file an issue, and we&#39;ll try to help you out.</p><p>Is anything wrong, unclear, missing? Help us <a href="https://github.com/jorgebucaran/hyperapp/fork">improve this page</a>.</p><h2 id="stay-in-the-loop">Stay in the loop</h2><ul>
<li><a href="https://twitter.com/hyperappjs">Twitter</a></li>
<li><a href="https://github.com/jorgebucaran/awesome-hyperapp">Awesome</a></li>
<li><a href="https://www.reddit.com/r/hyperapp">/r/hyperapp</a></li>
</ul><h2 id="license">License</h2>

BIN
card.b7cfd6ff.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

4
close.d10ab7e3.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.39355 0.979187L25.021 23.6066L23.6068 25.0208L0.979341 2.3934L2.39355 0.979187Z" fill="#091226"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.6064 0.979187L0.979029 23.6066L2.39324 25.0208L25.0207 2.3934L23.6064 0.979187Z" fill="#091226"/>
</svg>

After

Width:  |  Height:  |  Size: 408 B

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="237px" height="57px" viewBox="0 0 237 57" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>DoMoreWithLess</title>
<desc>Created with Sketch.</desc>
<g id="DoMoreWithLess" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M29.5,1.99859856 L0.866836421,51.5 L58.1331636,51.5 L29.5,1.99859856 Z" id="Triangle" stroke="#091226"></path>
<rect id="Rectangle" fill="#1661EE" x="186" y="1" width="51" height="51"></rect>
<circle id="Oval" stroke="#091226" cx="86.5" cy="26.5" r="25"></circle>
<path d="M148.5,0.618033989 L121.982675,19.8839987 L132.111392,51.0569843 L164.888608,51.0569843 L175.017325,19.8839987 L148.5,0.618033989 Z" id="Polygon" stroke="#091226"></path>
<line x1="0" y1="11" x2="175" y2="42" id="Path-2" stroke="#091226"></line>
</g>
</svg>

After

Width:  |  Height:  |  Size: 997 B

1
ecosystem.0b358926.html Normal file
View File

@ -0,0 +1 @@
<h1 id="ecosystem">ecosystem</h1><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In lobortis dignissim tellus, eget vestibulum leo feugiat vel. Fusce ac odio at nulla feugiat tincidunt. Cras eu metus varius, placerat ex nec, commodo felis. Morbi ac tempus ligula, eget finibus mi. Maecenas porttitor est a lacus suscipit luctus. Suspendisse ornare mattis purus eu mollis. Maecenas pellentesque sem quam, eu imperdiet ante lobortis in. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur purus lectus, sollicitudin in tempus vel, dignissim vitae nibh. In eu elit non elit dapibus dignissim eu congue diam. Nullam ut tellus et erat egestas consectetur eu ut lorem.</p>

212
examples.md Normal file
View File

@ -0,0 +1,212 @@
# Examples
## [Counter](https://codesandbox.io/s/hyperapp-playground-fwjlo)
```js
import { h, app } from "https://unpkg.com/hyperapp"
app({
init: 0,
view: state =>
h("div", {}, [
h("h1", {}, state),
h("button", { onClick: state => state - 1 }, "-"),
h("button", { onClick: state => state + 1 }, "+")
]),
node: document.getElementById("app")
})
```
## [Calculator](https://codesandbox.io/s/hyperapp-calculator-v8y5h)
```js
import { h, app } from "hyperapp"
const computer = {
"+": (a, b) => a + b,
"-": (a, b) => a - b,
"×": (a, b) => a * b,
"÷": (a, b) => a / b
}
const initialState = {
fn: "",
carry: 0,
value: 0,
hasCarry: false
}
const Clear = () => initialState
const NewDigit = (state, number) => ({
...state,
hasCarry: false,
value: (state.hasCarry ? 0 : state.value) * 10 + number
})
const NewFunction = (state, fn) => ({
...state,
fn,
hasCarry: true,
carry: state.value,
value:
state.hasCarry || !state.fn
? state.value
: computer[state.fn](state.carry, state.value)
})
const Equal = state => ({
...state,
hasCarry: true,
carry: state.hasCarry ? state.carry : state.value,
value: state.fn
? computer[state.fn](
state.hasCarry ? state.value : state.carry,
state.hasCarry ? state.carry : state.value
)
: state.value
})
const Calculator = state =>
h("main", {}, [
Display(state.value),
Keypad([
Functions({ keys: Object.keys(computer) }),
Digits({ keys: [7, 8, 9, 4, 5, 6, 1, 2, 3, 0] }),
AC,
EQ
])
])
const Display = value => h("div", { class: "display" }, value)
const Keypad = children => h("div", { class: "keys" }, children)
const Functions = props =>
props.keys.map(fn =>
h("button", { class: "function", onClick: [NewFunction, fn] }, fn)
)
const Digits = props =>
props.keys.map(digit =>
h(
"button",
{ class: { zero: digit === 0 }, onClick: [NewDigit, digit] },
digit
)
)
const AC = h("button", { onClick: Clear }, "AC")
const EQ = h("button", { onClick: Equal, class: "equal" }, "=")
app({
init: initialState,
view: Calculator,
node: document.getElementById("app")
})
```
## [Simple Clock](https://codesandbox.io/s/hyperapp-simple-clock-uhk59)
```js
import { h, app } from "hyperapp"
import { interval } from "@hyperapp/time"
const timeToUnits = t => [t.getHours(), t.getMinutes(), t.getSeconds()]
const formatTime = (hours, minutes, seconds, use24) =>
(use24 ? hours : hours > 12 ? hours - 12 : hours) +
":" +
`${minutes}`.padStart(2, "0") +
":" +
`${seconds}`.padStart(2, "0") +
(use24 ? "" : ` ${hours > 12 ? "PM" : "AM"}`)
const posixToHumanTime = (time, use24) =>
formatTime(...timeToUnits(new Date(time)), use24)
const Tick = (state, time) => ({
...state,
time
})
const ToggleFormat = state => ({
...state,
use24: !state.use24
})
const getInitialState = time => ({
time,
use24: false
})
app({
init: getInitialState(Date.now()),
view: state =>
h("div", {}, [
h("h1", {}, posixToHumanTime(state.time, state.use24)),
h("fieldset", {}, [
h("legend", {}, "Settings"),
h("label", {}, [
h("input", {
type: "checkbox",
checked: state.use24,
onInput: ToggleFormat
}),
"Use 24 Hour Clock"
])
])
]),
subscriptions: state => interval(Tick, { delay: 1000 }),
node: document.getElementById("app")
})
```
## [Todo App](https://codesandbox.io/s/hyperapp-todo-app-m3ctx)
```js
import { h, app } from "hyperapp"
import { preventDefault, targetValue } from "@hyperapp/events"
const getInitialState = items => ({ items, value: "" })
const newItem = value => ({
value,
lastValue: "",
isEditing: false,
id: Math.random().toString(36)
})
const NewValue = (state, value) => ({ ...state, value })
const Add = state =>
state.value.length === 0
? state
: {
...state,
value: "",
items: state.items.concat(newItem(state.value))
}
const TodoList = items =>
h("ol", {}, items.map(item => h("li", {}, item.value)))
app({
init: getInitialState([newItem("Take out the trash")]),
view: state =>
h("div", {}, [
h("h1", {}, "What needs done?"),
TodoList(state.items),
h("form", { onSubmit: preventDefault(Add) }, [
h("label", {}, [
h("input", {
value: state.value,
onInput: [NewValue, targetValue]
})
]),
h("button", {}, `New #${state.items.length + 1}`)
])
]),
node: document.getElementById("app")
})
```

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="220px" height="77px" viewBox="0 0 220 77" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>FasterThanReact</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="100%" y1="50%" x2="1.56777872%" y2="50%" id="linearGradient-1">
<stop stop-color="#091226" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="100%" y1="50%" x2="0%" y2="50%" id="linearGradient-2">
<stop stop-color="#1661EE" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
</defs>
<g id="FasterThanReact" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" fill="url(#linearGradient-1)" x="0" y="0" width="110" height="51" rx="25.5"></rect>
<rect id="Rectangle" fill="#091226" x="59" y="0" width="51" height="51" rx="25.5"></rect>
<rect id="Rectangle" fill="url(#linearGradient-2)" x="0" y="26" width="220" height="51"></rect>
<rect id="Rectangle" fill="#1661EE" x="169" y="26" width="51" height="51"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
favicon.648f6921.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

1
global.89bdf38a.css Normal file

File diff suppressed because one or more lines are too long

1
guides.31d0dce1.html Normal file
View File

@ -0,0 +1 @@
<h1 id="guides">guides</h1><p>lorem lorem</p><p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In id turpis et nibh commodo viverra et eu nisl. Pellentesque imperdiet dapibus aliquam. Cras vulputate viverra neque nec ultricies. Mauris consectetur vulputate eleifend. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas a auctor augue. Donec iaculis urna eget nisi maximus, vel aliquam magna volutpat. Mauris mattis ac augue non pharetra. Aliquam erat volutpat. Vestibulum ultricies, turpis ac pretium fringilla, quam ante tincidunt nisi, quis venenatis turpis odio sed libero. Nullam pulvinar tempor elit, gravida eleifend odio viverra non.</p>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="48px" height="40px" viewBox="0 0 48 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>hyperapp logo 2</title>
<desc>Created with Sketch.</desc>
<g id="hyperapp-logo-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M41.3330306,8.0000336 L15.792606,8.0000336 L18.7046283,0 L10.1911535,0 L-2.52242671e-13,28.0000335 L8.51313863,28.0000335 L12.8810881,16.0000672 L38.4208402,16.0000672 L36.9649132,20 L15.7041744,20 L11.325129,31.9999663 L11.325129,32.0001344 C11.0872379,32.6534522 10.9655184,33.3435887 10.9655184,34.0389368 C10.9655184,37.3090566 13.6564617,40 16.9265816,40 L29.6851095,40 L32.5969637,31.9999663 L19.8384358,31.9999663 L21.305627,28.0000335 L34.0530589,28.0000335 L32.5971318,31.9999663 L41.1106067,31.9999663 L46.9344832,16.0000672 L46.9344832,15.9998991 C47.1723743,15.3465812 47.2940938,14.6564449 47.2940938,13.9610968 C47.2940938,10.6909769 44.6031506,8.0000336 41.3330306,8.0000336 Z" id="Path" fill="#1661EE" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="64px" height="40px" viewBox="0 0 64 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>logo</title>
<desc>Created with Sketch.</desc>
<g id="logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M22.8857322,16.6248298 L36.0958035,16.6248298 L39.2694009,23.2280701 L22.8857322,23.2280701 L22.8857322,39.8534799 L16.3798112,39.8534799 L16.3798112,23.2280701 L3.55271368e-14,23.2280701 L3.55271368e-14,16.6248298 L16.3798112,16.6248298 L16.3798112,1.52766688e-13 L22.8857322,1.52766688e-13 L22.8857322,16.6248298 Z M63.6333879,39.8534799 L55.9695221,39.8534799 L36.8179296,1.52766688e-13 L44.4823669,1.52766688e-13 L63.6333879,39.8534799 Z" id="Fill-2" fill="#1661EE"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 902 B

16
hypercharged.f1b19631.svg Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="237px" height="51px" viewBox="0 0 237 51" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>Hypercharged</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="100%" y1="50%" x2="0%" y2="50%" id="linearGradient-1">
<stop stop-color="#1661EE" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Hypercharged" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" fill="url(#linearGradient-1)" x="0" y="0" width="237" height="51"></rect>
<rect id="Rectangle" fill="#1661EE" x="186" y="0" width="51" height="51"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 886 B

BIN
icon-192x192.c99be655.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
icon-512x512.ebbe6b77.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

1
index.html Normal file
View File

@ -0,0 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="x-ua-compatible" content="IE=edge,chrome=1"><title>Hyperapp • The tiny framework for building web interfaces</title><meta name="description" content="The tiny framework for building web interfaces."><link rel="home" href="https://hyperapp.dev"><meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1"><link rel="icon" type="image/png" href="/favicon.648f6921.png"><link rel="canonical" href="https://hyperapp.dev"><meta property="og:title" content="Hyperapp"><meta property="og:description" content="The tiny framework for building web interfaces."><meta property="og:type" content="website"><meta property="og:url" content="https://hyperapp.dev"><meta property="og:image" content="/card.b7cfd6ff.png"><meta property="og:image:secure_url" content="/card.b7cfd6ff.png"><meta property="og:image:type" content="image/png"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta property="og:image:alt" content="Hyperapp"><meta name="twitter:card" content="summary"><meta name="twitter:url" content="https://hyperapp.dev"><meta name="twitter:title" content="Hyperapp"><link rel="manifest" href="/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#ffffff"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="#ffffff"><meta name="apple-mobile-web-app-title" content="Hyperapp"><link rel="apple-touch-startup-image" href="/icon-192x192.c99be655.png"><link rel="apple-touch-icon" href="/icon-192x192.c99be655.png"><link rel="apple-touch-icon" sizes="192x192" href="/icon-192x192.c99be655.png"><link rel="apple-touch-icon" sizes="512x512" href="/icon-512x512.ebbe6b77.png"><meta name="msapplication-TileImage" content="/icon-512x512.ebbe6b77.png"><meta name="msapplication-TileColor" content="#ffffff"><link href="/global.89bdf38a.css" rel="stylesheet"><script defer="" src="/src.07c797be.js"></script><link rel="stylesheet" href="/src.34f9ddf1.css"></head><body> <div id="app"></div> </body></html>

4
logo-big.e296631b.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="153" height="30" viewBox="0 0 153 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.4836 12.5145H27.5755L30 17.4851H17.4836V30H12.5134V17.4851H0V12.5145H12.5134V0H17.4836V12.5145ZM49 30H43.284L29 0H34.7164L49 30Z" fill="#1661EE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.3071 10.2826H80.144L75.9899 21.8866C75.5366 23.1532 74.3152 24 72.9443 24H71.1804V22.6204H72.8227C73.5313 22.6204 74.1646 22.1874 74.4101 21.5368L74.5056 21.2818H73.9076L69.9775 10.2826H71.8786L75.125 19.9652L78.3071 10.2826ZM91.6992 15.7615C91.6992 19.1077 90.2891 21.5118 87.3634 21.5118C86.146 21.5118 85.1421 21.0942 84.3948 20.257V24H82.6217V10.2826H84.117L84.2663 11.809C85.0999 10.6805 86.2744 10.0322 87.6412 10.0322C90.6312 10.0322 91.6992 12.3111 91.6992 15.7615ZM89.7981 15.7615C89.7981 12.8762 88.9866 11.4332 87.214 11.4332C85.9538 11.4332 84.9928 12.3326 84.3948 13.2106V18.6894C84.9714 19.547 85.9109 20.0695 86.9789 20.0695C88.8586 20.0695 89.7981 18.7104 89.7981 15.7615ZM64.3605 10.0322C63.0366 10.0322 61.6484 10.6385 60.7725 11.7885V7H59V21.2818H60.7725V13.2524C61.3491 12.395 62.5023 11.4122 63.8053 11.4122C65.1299 11.4122 65.7493 11.9976 65.7493 13.3143V21.2818H67.5219V13.2938C67.5219 11.245 66.5603 10.0322 64.3605 10.0322ZM128.092 20.2996L127.666 21.5323C126.704 21.4082 125.935 20.948 125.616 19.9652C124.803 20.9894 123.544 21.5118 122.092 21.5118C119.827 21.5118 118.46 20.1744 118.46 18.1249C118.46 15.8664 120.318 14.5906 123.736 14.5906H125.379V13.6707C125.379 12.0815 124.376 11.4955 122.71 11.4955C121.963 11.4955 120.938 11.6423 119.784 12.039L119.314 10.7429C120.681 10.2413 121.878 10.0322 122.966 10.0322C125.764 10.0322 127.174 11.4122 127.174 13.5863V18.8985C127.174 19.777 127.473 20.1114 128.092 20.2996ZM125.379 15.7825H123.758C121.258 15.7825 120.36 16.6819 120.36 18.104C120.36 19.484 121.065 20.1947 122.539 20.1947C123.65 20.1947 124.718 19.6302 125.379 18.6271V15.7825ZM104.004 15.4481C104.004 15.8245 103.982 16.1594 103.961 16.3889H96.3998C96.4856 18.9609 97.9588 20.0695 99.6891 20.0695C100.714 20.0695 101.718 19.8184 102.701 19.0442L103.534 20.2367C102.487 21.0318 101.077 21.5118 99.6891 21.5118C96.3998 21.5118 94.5416 19.212 94.5416 15.7825C94.5416 12.4579 96.4636 10.0322 99.3684 10.0322C102.274 10.0322 104.004 12.1026 104.004 15.4481ZM102.295 15.0503C102.252 12.688 101.163 11.4752 99.3898 11.4752C97.788 11.4752 96.5492 12.6256 96.3998 15.0503H102.295ZM140.61 15.7615C140.61 19.1077 139.2 21.5118 136.275 21.5118C135.057 21.5118 134.053 21.0942 133.306 20.257V24H131.533V10.2826H133.028L133.178 11.809C134.011 10.6805 135.185 10.0322 136.552 10.0322C139.543 10.0322 140.61 12.3111 140.61 15.7615ZM138.709 15.7615C138.709 12.8762 137.898 11.4332 136.125 11.4332C134.865 11.4332 133.904 12.3326 133.306 13.2106V18.6894C133.883 19.547 134.822 20.0695 135.89 20.0695C137.77 20.0695 138.709 18.7104 138.709 15.7615ZM153 15.7615C153 19.1077 151.59 21.5118 148.664 21.5118C147.447 21.5118 146.443 21.0942 145.695 20.257V24H143.922V10.2826H145.418L145.567 11.809C146.401 10.6805 147.575 10.0322 148.942 10.0322C151.932 10.0322 153 12.3111 153 15.7615ZM151.099 15.7615C151.099 12.8762 150.287 11.4332 148.515 11.4332C147.255 11.4332 146.293 12.3326 145.695 13.2106V18.6894C146.272 19.547 147.211 20.0695 148.28 20.0695C150.159 20.0695 151.099 18.7104 151.099 15.7615ZM111.41 12.395L111.196 10.2826H107.031V11.6423H109.659V19.9023H107.031V21.2818H114.593V19.9023H111.431V14.6954C111.815 13.1692 112.99 11.7262 114.506 11.7262C114.976 11.7262 115.319 11.7885 115.81 11.9144L116.13 10.1993C115.596 10.0735 115.211 10.0322 114.656 10.0322C113.31 10.0322 112.072 10.8261 111.41 12.395Z" fill="#091226"/>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

1
manifest.webmanifest Normal file
View File

@ -0,0 +1 @@
{"short_name":"Hyperapp","name":"Hyperapp","description":"Hyperapp with a modern configuration and best-practices in mind.","display":"fullscreen","scope":"/","start_url":"/","theme_color":"white","background_color":"white","orientation":"portrait","dir":"ltr","lang":"en","icons":[{"src":"icon-192x192.c99be655.png","sizes":"192x192","type":"image/png"},{"src":"icon-512x512.ebbe6b77.png","sizes":"512x512","type":"image/png"}]}

5
menu.1f287146.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="32" height="22" viewBox="0 0 32 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 10H32V12H0V10Z" fill="#091226"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H32V2H0L0 0Z" fill="#091226"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 20H32V22H0L0 20Z" fill="#091226"/>
</svg>

After

Width:  |  Height:  |  Size: 365 B

1
pages-data.json Normal file

File diff suppressed because one or more lines are too long

3
quickstart.0e6b9c83.html Normal file
View File

@ -0,0 +1,3 @@
<h2 id="quickstart">quickstart</h2><p>1 Install Hyperapp with npm or Yarn:</p><hr><pre><code class="language-console">npm i hyperapp</code></pre><hr><p>2 Then with a module bundler like <a href="https://parceljs.org">Parcel</a> or <a href="https://webpack.js.org">Webpack</a> import it in your application and get right down to business.</p><hr><pre><code class="language-js">import { h, app } from &quot;hyperapp&quot;</code></pre><hr><p>3 Don&#39;t want to set up a build step? Import Hyperapp in a <code>&lt;script&gt;</code> tag as a module. Don&#39;t worry; modules are supported in all evergreen, self-updating desktop, and mobile browsers.</p><hr><pre><code class="language-html">&lt;script type=&quot;module&quot;&gt;
import { h, app } from &quot;https://unpkg.com/hyperapp&quot;
&lt;/script&gt;</code></pre><hr><p>Here&#39;s the first example to get you started: a counter that can go up or down. You can try it online <a href="https://codesandbox.io/s/hyperapp-playground-fwjlo">here</a>.</p><hr>

16
ref.md Normal file
View File

@ -0,0 +1,16 @@
# API
> Technical reference for Hyperapp core APIs and packages.
Under construction. See [Examples](examples.md).
## Index
- [Core]()
- [`h()`](#h-)
- [`app()`](#app-)
- [`Lazy()`](#Lazy-)
- [@hyperapp/time](#)
- [@hyperapp/http](#)
- [@hyperapp/events](#)
- [@hyperapp/random](#)

5
robots.txt Normal file
View File

@ -0,0 +1,5 @@
# www.robotstxt.org/
# Allow crawling of all content
User-agent: *
Disallow:

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="10px" height="10px" viewBox="0 0 10 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>SoSmallCantEven</title>
<desc>Created with Sketch.</desc>
<g id="SoSmallCantEven" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" fill="#1661EE" x="0" y="0" width="10" height="10"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 516 B

1
sponsor.ef925587.html Normal file
View File

@ -0,0 +1 @@
<h1 id="sponsor">sponsor</h1><p>lorem lorem lorem</p><p>In urna ex, finibus sit amet laoreet id, pharetra placerat lorem. Suspendisse laoreet pulvinar nunc, sed tristique ex venenatis tristique. Quisque non vulputate enim, vitae facilisis sapien. Nunc sagittis vel mi et tristique. In ornare leo et lectus ornare, vel pretium odio vulputate. Nam rhoncus quam vel neque rhoncus rutrum. Quisque posuere, purus sit amet ornare blandit, massa ligula sagittis magna, ut interdum purus neque et nisl. Integer eros sapien, faucibus at est vel, rhoncus gravida arcu. In volutpat sapien neque, vel malesuada sapien aliquam at.</p>

80
src.07c797be.js Normal file

File diff suppressed because one or more lines are too long

1
src.34f9ddf1.css Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="77px" height="77px" viewBox="0 0 77 77" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>TimeToInteractive</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="50%" y1="14.9928291%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#1661EE" stop-opacity="0.75" offset="0%"></stop>
<stop stop-color="#1661EE" stop-opacity="0.5" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="85.1803174%" x2="50%" y2="3.061617e-15%" id="linearGradient-2">
<stop stop-color="#1661EE" stop-opacity="0.25" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="100%" y1="50%" x2="14.6349205%" y2="50%" id="linearGradient-3">
<stop stop-color="#1661EE" stop-opacity="0.25" offset="0%"></stop>
<stop stop-color="#1661EE" stop-opacity="0.5" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-1.11022302e-14%" y1="50%" x2="100%" y2="50%" id="linearGradient-4">
<stop stop-color="#1661EE" stop-opacity="0.75" offset="0%"></stop>
<stop stop-color="#1661EE" offset="100%"></stop>
</linearGradient>
</defs>
<g id="TimeToInteractive" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" fill="url(#linearGradient-1)" x="0" y="0" width="10" height="67"></rect>
<rect id="Rectangle" fill="url(#linearGradient-2)" x="67" y="10" width="10" height="67"></rect>
<rect id="Rectangle" fill="url(#linearGradient-3)" x="0" y="67" width="67" height="10"></rect>
<rect id="Rectangle" fill="url(#linearGradient-4)" x="10" y="0" width="67" height="10"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

501
tutorial.9811de31.html Normal file
View File

@ -0,0 +1,501 @@
<h1 id="tutorial">tutorial</h1><p>===================================</p><p>Welcome! If you&#39;re new to Hyperapp, you&#39;ve found the perfect place to start learning.</p><h2 id="the-set-up">The Set-up</h2><p>Together we&#39;ll build a simple newsreader-like application. As we do, we&#39;ll work
our way through the five core concepts: view, state, actions, effects and subscriptions.</p><p>To move things along, let&#39;s imagine we&#39;ve already made a static version of the
app we want to build, with this HTML:</p><pre><code class="language-html">&lt;div id=&quot;app&quot; class=&quot;container&quot;&gt;
&lt;div class=&quot;filter&quot;&gt;
Filter:
&lt;span class=&quot;filter-word&quot;&gt;ocean&lt;/span&gt;
&lt;button&gt;&amp;#9998;&lt;/button&gt;
&lt;/div&gt;
&lt;div class=&quot;stories&quot;&gt;
&lt;ul&gt;
&lt;li class=&quot;unread&quot;&gt;
&lt;p class=&quot;title&quot;&gt;The &lt;em&gt;Ocean &lt;/em&gt;is Sinking&lt;/p&gt;
&lt;p class=&quot;author&quot;&gt;Kat Stropher&lt;/p&gt;
&lt;/li&gt;
&lt;li class=&quot;reading&quot;&gt;
&lt;p class=&quot;title&quot;&gt;&lt;em&gt;Ocean &lt;/em&gt;life is brutal&lt;/p&gt;
&lt;p class=&quot;author&quot;&gt;Surphy McBrah&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p class=&quot;title&quot;&gt;
Family friendly fun at the
&lt;em&gt;ocean &lt;/em&gt;exhibit
&lt;/p&gt;
&lt;p class=&quot;author&quot;&gt;Guy Prosales&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class=&quot;story&quot;&gt;
&lt;h1&gt;Ocean life is brutal&lt;/h1&gt;
&lt;p&gt;
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.
&lt;/p&gt;
&lt;p class=&quot;signature&quot;&gt;Surphy McBrah&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;autoupdate&quot;&gt;
Auto update:
&lt;input type=&quot;checkbox&quot; /&gt;
&lt;/div&gt;
&lt;/div&gt;</code></pre><p>...and some CSS <a href="https://zaceno.github.com/hatut/style.css">here</a>.</p><p>It looks like this:</p><p>We&#39;ll start by making Hyperapp render the HTML for us. Then we will
add dynamic behavior to all the widgets, including text input and dynamically fetching stories.</p><p>First, let&#39;s begin with the traditional &quot;Hello World!&quot;</p><h2 id="hello-world">Hello World</h2><p>Create this html file:</p><pre><code class="language-html">&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;https://zaceno.github.com/hatut/style.css&quot;&gt;
&lt;script type=&quot;module&quot;&gt;
// -- IMPORTS --
import {h, app} from &quot;https://unpkg.com/hyperapp?module&quot;
// -- ACTIONS --
// -- VIEWS ---
// -- RUN --
app({
node: document.getElementById(&quot;app&quot;),
view: () =&gt; h(&quot;h1&quot;, {}, [
&quot;Hello &quot;,
h(&quot;i&quot;, {}, &quot;World!&quot;)
])
})
&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id=&quot;app&quot;&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><blockquote>
<p>The section structure outlined in the comments is not important. It&#39;s
just a suggestion for how to organize the code we&#39;ll be
adding throughout the tutorial.</p>
</blockquote><p>Open it in a browser, and you&#39;ll be greeted with an optimistic <strong>Hello <em>World!</em></strong>.</p><h2 id="view">View</h2><p>Let&#39;s step through what just happened.</p><h3 id="virtual-nodes">Virtual Nodes</h3><p>Hyperapp exports the <code>app</code> and <code>h</code> functions.
<code>h</code> is for creating <em>virtual nodes</em>, which is to say: plain javascript objects
which <em>represent</em> DOM nodes.</p><p>The result of </p><pre><code class="language-js">h(&quot;h1&quot;, {}, [
&quot;Hello &quot;,
h(&quot;i&quot;, {}, &quot;World!&quot;)
])</code></pre><p>is a virtual node, representing</p><pre><code class="language-html">&lt;h1&gt;
Hello
&lt;i&gt;World!&lt;/i&gt;
&lt;/h1&gt;</code></pre><h3 id="rendering-to-the-dom">Rendering to the DOM</h3><p><code>app</code> is the function that runs our app. It is called with a single argument - an object
which can take several properties. For now we&#39;re just concerned with <code>view</code> and <code>node.</code></p><p>Hyperapp calls the <code>view</code> function which tells it the DOM structure we want, in the form
of virtual nodes. Hyperapp proceeds to create it for us, replacing the node specified in <code>node</code>.</p><p>To render the HTML we want, change the <code>view</code> to:</p><pre><code class="language-js">view: () =&gt; h(&quot;div&quot;, {id: &quot;app&quot;, class: &quot;container&quot;}, [
h(&quot;div&quot;, {class: &quot;filter&quot;}, [
&quot; Filter: &quot;,
h(&quot;span&quot;, {class: &quot;filter-word&quot;}, &quot;ocean&quot;),
h(&quot;button&quot;, {}, &quot;\u270E&quot;)
]),
h(&quot;div&quot;, {class: &quot;stories&quot;}, [
h(&quot;ul&quot;, {}, [
h(&quot;li&quot;, {class: &quot;unread&quot;}, [
h(&quot;p&quot;, {class: &quot;title&quot;}, [
&quot;The &quot;,
h(&quot;em&quot;, {}, &quot;Ocean&quot;),
&quot; is Sinking!&quot;
]),
h(&quot;p&quot;, {class: &quot;author&quot;}, &quot;Kat Stropher&quot;)
]),
h(&quot;li&quot;, {class: &quot;reading&quot;}, [
h(&quot;p&quot;, {class: &quot;title&quot;}, [
h(&quot;em&quot;, {}, &quot;Ocean&quot;),
&quot; life is brutal&quot;
]),
h(&quot;p&quot;, {class: &quot;author&quot;}, &quot;Surphy McBrah&quot;),
]),
h(&quot;li&quot;, {}, [
h(&quot;p&quot;, {class: &quot;title&quot;}, [
&quot;Family friendly fun at the &quot;,
h(&quot;em&quot;, {}, &quot;ocean&quot;),
&quot; exhibit&quot;
]),
h(&quot;p&quot;, {class: &quot;author&quot;}, &quot;Guy Prosales&quot;)
])
])
]),
h(&quot;div&quot;, {class: &quot;story&quot;}, [
h(&quot;h1&quot;, {}, &quot;Ocean life is brutal&quot;),
h(&quot;p&quot;, {}, `
Lorem ipsum dolor sit amet, consectetur adipiscing
elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat.
`),
h(&quot;p&quot;, {class: &quot;signature&quot;}, &quot;Surphy McBrah&quot;)
]),
h(&quot;div&quot;, {class: &quot;autoupdate&quot;}, [
&quot;Auto update: &quot;,
h(&quot;input&quot;, {type: &quot;checkbox&quot;})
])
]),</code></pre><p>Try it out to confirm that the result matches the screenshot above.</p><blockquote>
<p>In many frameworks it is common to write your views/templates
using syntax that looks like HTML. This is possible with Hyperapp as well.
<a href="https://babeljs.io/docs/en/babel-plugin-transform-react-jsx">JSX</a> can compile a HTML-like syntax into <code>h</code> calls at build-time. If you&#39;d rather
not use a build system, <a href="https://github.com/developit/htm">htm</a> does the same at run-time.</p>
<p>In this tutorial we&#39;ll stick with <code>h</code> to keep it simple and close to the metal.</p>
</blockquote><h3 id="composing-the-view-with-reusable-functions">Composing the view with reusable functions</h3><p>The great thing about using plain functions to build up our virtual DOM
is that we can break out repetitive or complicated parts into their own functions.</p><p>Add this function (in the &quot;VIEWS&quot; section):</p><pre><code class="language-js">const emphasize = (word, string) =&gt;
string.split(&quot; &quot;).map(x =&gt; {
if (x.toLowerCase() === word.toLowerCase()) {
return h(&quot;em&quot;, {}, x + &quot; &quot;)
} else {
return x + &quot; &quot;
}
}) </code></pre><p>It lets you change this: </p><pre><code class="language-js"> ...
h(&quot;p&quot;, {class: &quot;title&quot;}, [
&quot;The &quot;,
h(&quot;em&quot;, {}, &quot;Ocean&quot;),
&quot; is Sinking!&quot;
]),
...</code></pre><p>into this:</p><pre><code class="language-js"> ...
h(&quot;p&quot;, {class: &quot;title&quot;}, emphasize(&quot;ocean&quot;,
&quot;The Ocean is Sinking&quot;
))
...</code></pre><p>Story thumbnails are repeated several times, so encapsulate
them in their own function:</p><pre><code class="language-js">const StoryThumbnail = props =&gt; h(
&quot;li&quot;,
{class: {
unread: props.unread,
reading: props.reading,
}},
[
h(&quot;p&quot;, {class: &quot;title&quot;}, emphasize(props.filter, props.title)),
h(&quot;p&quot;, {class: &quot;author&quot;}, props.author)
]
)</code></pre><blockquote>
<p>The last example demonstrates a helpful feature of the <code>class</code> property. When
you set it to an object rather than a string, each key with a truthy value
will become a class in the class list.</p>
</blockquote><p>Continue by creating functions for each section of the view:</p><pre><code class="language-js">
const StoryList = props =&gt; h(&quot;div&quot;, {class: &quot;stories&quot;}, [
h(&quot;ul&quot;, {}, Object.keys(props.stories).map(id =&gt;
StoryThumbnail({
id,
title: props.stories[id].title,
author: props.stories[id].author,
unread: !props.stories[id].seen,
reading: props.reading === id,
filter: props.filter,
})
))
])
const Filter = props =&gt; h(&quot;div&quot;, {class: &quot;filter&quot;}, [
&quot;Filter:&quot;,
h(&quot;span&quot;, {class: &quot;filter-word&quot;}, props.filter),
h(&quot;button&quot;, {}, &quot;\u270E&quot;)
])
const StoryDetail = props =&gt; h(&quot;div&quot;, {class: &quot;story&quot;}, [
props &amp;&amp; h(&quot;h1&quot;, {}, props.title),
props &amp;&amp; h(&quot;p&quot;, {}, `
Lorem ipsum dolor sit amet, consectetur adipiscing
elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, qui
nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat.
`),
props &amp;&amp; h(&quot;p&quot;, {class: &quot;signature&quot;}, props.author)
])
const AutoUpdate = props =&gt; h(&quot;div&quot;, {class: &quot;autoupdate&quot;}, [
&quot;Auto update: &quot;,
h(&quot;input&quot;, {type: &quot;checkbox&quot;})
])
const Container = content =&gt; h(&quot;div&quot;, {class: &quot;container&quot;}, content)
</code></pre><p>With those the view can be written as:</p><pre><code class="language-js">view: () =&gt; Container([
Filter({
filter: &quot;ocean&quot;
}),
StoryList({
stories: {
&quot;112&quot;: {
title: &quot;The Ocean is Sinking&quot;,
author: &quot;Kat Stropher&quot;,
seen: false,
},
&quot;113&quot;: {
title: &quot;Ocean life is brutal&quot;,
author: &quot;Surphy McBrah&quot;,
seen: true,
},
&quot;114&quot;: {
title: &quot;Family friendly fun at the ocean exhibit&quot;,
author: &quot;Guy Prosales&quot;,
seen: true,
}
},
reading: &quot;113&quot;,
filter: &quot;ocean&quot;
}),
StoryDetail({
title: &quot;Ocean life is brutal&quot;,
author: &quot;Surphy McBrah&quot;,
}),
AutoUpdate(),
])</code></pre><p>What you see on the page should be exactly the same as before, because we haven&#39;t
changed what <code>view</code> returns. Using basic functional composition, we were able to make
the code a bit more manageable, and that&#39;s the only difference.</p><h2 id="state">State</h2><p>With all that view logic broken out in separate functions, <code>view</code> is starting to look like
plain <em>data</em>. The next step is to fully separate data from the view.</p><p>Add an <code>init</code> property to your app, with this pure data:</p><pre><code class="language-js"> init: {
filter: &quot;ocean&quot;,
reading: &quot;113&quot;,
stories: {
&quot;112&quot;: {
title: &quot;The Ocean is Sinking&quot;,
author: &quot;Kat Stropher&quot;,
seen: false,
},
&quot;113&quot;: {
title: &quot;Ocean life is brutal&quot;,
author: &quot;Surphy McBrah&quot;,
seen: true,
},
&quot;114&quot;: {
title: &quot;Family friendly fun at the ocean exhibit&quot;,
author: &quot;Guy Prosales&quot;,
seen: true,
}
}
},</code></pre><p>The value of <code>init</code> becomes the app&#39;s <em>state</em>. Hyperapp calls <code>view</code> with the state
as an argument, so it can be reduced to:</p><pre><code class="language-js"> view: state =&gt; Container([
Filter(state),
StoryList(state),
StoryDetail(state.reading &amp;&amp; state.stories[state.reading]),
AutoUpdate(state),
]),</code></pre><p>Visually, everything is <em>still</em> the same. If you&#39;d like to see a working example of the code so far, have a look <a href="https://codesandbox.io/s/hyperapp-tutorial-step-1-gq662">here</a></p><h2 id="actions">Actions</h2><p>Now that we know all about rendering views, it&#39;s finally time for some <em>action</em>!</p><h3 id="reacting-to-events-in-the-dom">Reacting to events in the DOM</h3><p>The first bit of dynamic behavior we will add is so that when you click
the pencil-button, a text input with the filter word appears.</p><p>Add an <code>onClick</code> property to the button in the filter view:</p><pre><code class="language-js">const Filter = props =&gt; h(&quot;div&quot;, {class: &quot;filter&quot;}, [
&quot;Filter:&quot;,
h(&quot;span&quot;, {class: &quot;filter-word&quot;}, props.filter),
h(&quot;button&quot;, { onClick: StartEditingFilter }, &quot;\u270E&quot;) // &lt;---
])</code></pre><p>This makes Hyperapp bind a click-event handler on the button element, so
that when the button is clicked, an action named <code>StartEditingFilter</code> is
<em>dispatched</em>. Create the action in the &quot;ACTIONS&quot; section:</p><pre><code class="language-js">const StartEditingFilter = state =&gt; ({...state, editingFilter: true})</code></pre><p>Actions are just functions describing transformations of the state.
This action keeps everything in the state the same except for <code>editingFilter</code>
which it sets to <code>true</code>.</p><p>When Hyperapp dispatches an action, it replaces the old state with the new
one calculated using the action. Then the DOM is modified to match what the
view returns for this new state.</p><p>When <code>editingFilter</code> is true, we want to have a text input instead of a span with the filter word. We can express this in the <code>Filter</code> view using a
ternary operator (<code>a ? b : c</code>).</p><pre><code class="language-js">const Filter = props =&gt; h(&quot;div&quot;, {class: &quot;filter&quot;}, [
&quot;Filter:&quot;,
props.editingFilter // &lt;---
? h(&quot;input&quot;, {type: &quot;text&quot;, value: props.filter}) // &lt;---
: h(&quot;span&quot;, {class: &quot;filter-word&quot;}, props.filter),
h(&quot;button&quot;, { onClick: StartEditingFilter }, &quot;\u270E&quot;)
])</code></pre><p>Now, when you click the pencil button the text input appears. But we still need to add
a way to go back. We need an action to <code>StopEditingFilter</code>, and a button to dispatch it.</p><p>Add the action:</p><pre><code class="language-js">const StopEditingFilter = state =&gt; ({...state, editingFilter: false})</code></pre><p>and update the <code>Filter</code> view again:</p><pre><code class="language-js">const Filter = props =&gt; h(&quot;div&quot;, {class: &quot;filter&quot;}, [
&quot;Filter:&quot;,
props.editingFilter
? h(&quot;input&quot;, {type: &quot;text&quot;, value: props.filter})
: h(&quot;span&quot;, {class: &quot;filter-word&quot;}, props.filter),
props.editingFilter // &lt;---
? h(&quot;button&quot;, {onClick: StopEditingFilter}, &quot;\u2713&quot;)
: h(&quot;button&quot;, {onClick: StartEditingFilter}, &quot;\u270E&quot;), // &lt;---
])</code></pre><p>When you click the pencil button, it is replaced with a check-mark button that can take you back to the first state.</p><h3 id="capturing-event-data-in-actions">Capturing event-data in actions</h3><p>The next step is to use the input for editing the filter word. Whatever we
type in the box should be emphasized in the story-list.</p><p>Update the <code>Filter</code> view yet again:</p><pre><code class="language-js">const Filter = props =&gt; h(&quot;div&quot;, {class: &quot;filter&quot;}, [
&quot;Filter:&quot;,
props.editingFilter
? h(&quot;input&quot;, {
type: &quot;text&quot;,
value: props.filter,
onInput: SetFilter, // &lt;----
})
: h(&quot;span&quot;, {class: &quot;filter-word&quot;}, props.filter),
props.editingFilter
? h(&quot;button&quot;, {onClick: StopEditingFilter}, &quot;\u2713&quot;)
: h(&quot;button&quot;, {onClick: StartEditingFilter}, &quot;\u270E&quot;),
])</code></pre><p>This will dispatch the <code>SetFilter</code> action everytime someone types in the input. Implement the action like this:</p><pre><code class="language-js">const SetFilter = (state, event) =&gt; ({...state, filter: event.target.value})</code></pre><p>The second argument to an action is known as the <em>payload</em>. Actions
dispatched in response to an events on DOM elements receive the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event">event object</a> for a payload. <code>event.target</code> refers to the input element in the DOM, and
<code>event.target.value</code> refers to the current value entered into it.</p><p>Now see what happens when you erase &quot;ocean&quot; and type &quot;friendly&quot; instead:</p><h3 id="actions-with-custom-payloads">Actions with custom payloads</h3><p>Next up: selecting stories by clicking them in the list.</p><p>The following action sets the <code>reading</code> property in the state to a story-id, which amounts to &quot;selecting&quot; the story:</p><pre><code class="language-js">const SelectStory = (state, id) =&gt; ({...state, reading: id})</code></pre><p>It has a payload, but it&#39;s not an event object. It&#39;s a custom value telling us which
story was clicked. How are actions dispatched with custom payloads? Like this:</p><pre><code class="language-js">
const StoryThumbnail = props =&gt; h(
&quot;li&quot;,
{
onClick: [SelectStory, props.id], // &lt;----
class: {
unread: props.unread,
reading: props.reading,
}
},
[
h(&quot;p&quot;, {class: &quot;title&quot;}, emphasize(props.filter, props.title)),
h(&quot;p&quot;, {class: &quot;author&quot;}, props.author)
]
)</code></pre><p>Instead of just specifying the action, we give a length-2 array with the action first and the custom payload second.</p><p>Selecting stories works now, but the feature is not quite done. When a story is selected,
we need to set its <code>seen</code> property to <code>true</code>, so we can highlight which stories the user has yet to read. Update the <code>SelectStory</code> action:</p><pre><code class="language-js">const SelectStory = (state, id) =&gt; ({
...state, // keep all state the same, except for the following:
reading: id,
stories: {
...state.stories, //keep stories the same, except for:
[id]: {
...state.stories[id], //keep this story the same, except for:
seen: true,
}
}
})</code></pre><p>Now, when you select a blue-edged story it turns yellow because it is selected, and when you select something else,
the edge turns gray to indicate you&#39;ve read the story.</p><h3 id="payload-filters">Payload filters</h3><p>There&#39;s one little thing we should fix about <code>SetFilter</code>. See how it&#39;s dependent on the complex <code>event</code> object? It would be easier to test and reuse if it were simply:</p><pre><code class="language-js">const SetFilter = (state, word) =&gt; ({...state, filter: word})</code></pre><p>But we don&#39;t know the word beforehand, so how can we set it as a custom payload? Change the <code>Filter</code> view again (last time - I promise!):</p><pre><code class="language-js">const Filter = props =&gt; h(&quot;div&quot;, {class: &quot;filter&quot;}, [
&quot;Filter:&quot;,
props.editingFilter
? h(&quot;input&quot;, {
type: &quot;text&quot;,
value: props.filter,
onInput: [SetFilter, event =&gt; event.target.value], // &lt;----
})
: h(&quot;span&quot;, {class: &quot;filter-word&quot;}, props.filter),
props.editingFilter
? h(&quot;button&quot;, {onClick: StopEditingFilter}, &quot;\u2713&quot;)
: h(&quot;button&quot;, {onClick: StartEditingFilter}, &quot;\u270E&quot;),
])</code></pre><p>When we give a <em>function</em> as the custom payload, Hyperapp considers it a <em>payload filter</em> and passes the default
payload through it, providing the returned value as payload to the action.</p><blockquote>
<p>Payload filters are also useful when you need a payload that is a combination of custom data and event data</p>
</blockquote><p>If you&#39;d like to see a working example of the code so far, have a look <a href="https://codesandbox.io/s/hyperapp-tutorial-step-2-5yv34">here</a></p><h2 id="effects">Effects</h2><p>Until now, the list of stories has been defined in the state and doesn&#39;t change. What we really want is
for stories matching the filter to be dynamically loaded. When we click the check-mark button
(indicating we are done editing the filter), we want to query an API and display the stories it responds with.</p><h3 id="actions-can-return-effects">Actions can return effects</h3><p>Add this import (to the &quot;IMPORTS&quot; section):</p><pre><code class="language-js">import {Http} from &quot;https:/unpkg.com/hyperapp-fx@next?module&quot;</code></pre><p>Use the imported <code>Http</code> in the <code>StopEditingFilter</code> action like this:</p><pre><code class="language-js">const StopEditingFilter = state =&gt; [
{
...state,
editingFilter: false,
},
Http({ // &lt;---
url: `https://zaceno.github.io/hatut/data/${state.filter.toLowerCase()}.json`, // &lt;---
response: &quot;json&quot;, // &lt;---
action: GotStories, // &lt;---
})
]</code></pre><p>The call to <code>Http(...)</code> does <em>not</em> immediately execute the API request. <code>Http</code> is an <em>effect creator</em>. It returns
an <em>effect</em> bound to the options we provided. </p><p>When Hyperapp sees an action return an array, it takes the first element of the array to be the new state, and the rest to
be <em>effects</em>. Effects are executed by Hyperapp as part of processing the action&#39;s return value.</p><blockquote>
<p>Hyperapp provides effect creators for many common situations. If you&#39;ve got an unusual case or are working
with less common APIs you may need to implement your own effects. Don&#39;t worry - it&#39;s easy! See the <a href="">API reference</a> for more information.</p>
</blockquote><h3 id="effects-can-dispatch-actions">Effects can dispatch actions</h3><p>One of the options we passed to <code>Http</code> was <code>action: GotStories</code>. The way this effect works is that when the response comes
back from the api, an action named <code>GotStories</code> (yet to be implemented) will be dispatched, with the response body as the payload.</p><p>The response body is in json, but the payload will be a javascript object, thanks to the parsing hint <code>response: &quot;json&quot;</code>. It will look like this (although the details depend on your filter of course):</p><pre><code class="language-js">{
&quot;112&quot;: {
title: &quot;The Ocean is Sinking&quot;,
author: &quot;Kat Stropher&quot;,
},
&quot;113&quot;: {
title: &quot;Ocean life is brutal&quot;,
author: &quot;Surphy McBrah&quot;,
},
&quot;114&quot;: {
title: &quot;Family friendly fun at the ocean exhibit&quot;,
author: &quot;Guy Prosales&quot;,
}
}</code></pre><p>The job of <code>GotStories</code> is to load this data into the state, in place of the stories we already have there. As it
does, it should take care to remember which story was selected, and which stories we have seen, if they were already
in the previous state. This will be our most complex action yet, and it could look like this:</p><pre><code class="language-js">const GotStories = (state, response) =&gt; {
const stories = {}
Object.keys(response).forEach(id =&gt; {
stories[id] = {...response[id], seen: false}
if (state.stories[id] &amp;&amp; state.stories[id].seen) {
stories[id].seen = true
}
})
const reading = stories[state.reading] ? state.reading : null
return {
...state,
stories,
reading,
}
}</code></pre><p>Try it out! Enter &quot;life&quot; in the filter input. When you click the check-mark button some new
stories are loaded all with blue edges except for &quot;Ocean life is brutal&quot; because it is
still selected.</p><h3 id="running-effects-on-initialization">Running effects on initialization</h3><p>The next obvious step is to load the <em>initial</em> stories from the API as well. Change init to this:</p><pre><code class="language-js"> init: [
{
editingFilter: false,
autoUpdate: false,
filter: &quot;ocean&quot;,
reading: null,
stories: {}, // &lt;---
},
Http({ // &lt;---
url: `https://zaceno.github.io/hatut/data/ocean.json`, // &lt;---
response: &#39;json&#39;, // &lt;---
action: GotStories, // &lt;---
})
],</code></pre><p>Hyperapp treats the init-value the same way as it treats return values from actions. By adding the <code>Http</code> effect
in <code>init</code>, the app will fire the API request immediately, so we don&#39;t need the stories in the state from the start.</p><h3 id="tracking-state-for-asynchronous-effects">Tracking state for asynchronous effects</h3><p>If we could display a spinner while we wait for stories to load, it would make for a smoother user experience. To
do that, we will need a new state property to tell us if we&#39;re waiting for a repsonse - and
consequently wether or not to render the spinner.</p><p>Create this action: </p><pre><code class="language-js">const FetchStories = state =&gt; [
{...state, fetching: true},
Http({
url: `https://zaceno.github.io/hatut/data/${state.filter.toLowerCase()}.json`,
response: &#39;json&#39;,
action: GotStories,
})
]</code></pre><p>Instead of dispatching this action, we will use it to simplify <code>StopEditingFilter</code>:</p><pre><code class="language-js">const StopEditingFilter = state =&gt; FetchStories({...state, editingFilter: false})</code></pre><p>... and <code>init</code> as well:</p><pre><code class="language-js"> init: FetchStories({
editingFilter: false,
autoUpdate: false,
filter: &quot;ocean&quot;,
reading: null,
stories: {},
}),</code></pre><p>Now, when <code>StopEditingFilter</code> is dispatched, <em>and</em> at initialization, the API call goes out and the
<code>fetching</code> prop is set to <code>true</code>. Also, notice how we refactored out the repetitive use of <code>Http</code>.</p><p>We also need to set <code>fetching: false</code> in <code>GotStories</code>:</p><pre><code class="language-js">const GotStories = (state, response) =&gt; {
const stories = {}
Object.keys(response).forEach(id =&gt; {
stories[id] = {...response[id], seen: false}
if (state.stories[id] &amp;&amp; state.stories[id].seen) {
stories[id].seen = true
}
})
const reading = stories[state.reading] ? state.reading : null
return {
...state,
stories,
reading,
fetching: false, // &lt;---
}
}</code></pre><p>With this, we know that when <code>fetching</code> is <code>true</code> we are waiting for a response, and should display
the spinner in the <code>StoryList</code> view:</p><pre><code class="language-js">const StoryList = props =&gt; h(&quot;div&quot;, {class: &quot;stories&quot;}, [
props.fetching &amp;&amp; h(&quot;div&quot;, {class: &quot;loadscreen&quot;}, [ // &lt;---
h(&quot;div&quot;, {class: &quot;spinner&quot;}) // &lt;---
]), // &lt;---
h(&quot;ul&quot;, {}, Object.keys(props.stories).map(id =&gt;
StoryThumbnail({
id,
title: props.stories[id].title,
author: props.stories[id].author,
unread: !props.stories[id].seen,
reading: props.reading === id,
filter: props.filter
})
))
])</code></pre><p>When the app loads, and when you change the filter, you should see the spinner appear until the stories are loaded.</p><blockquote>
<p>If you aren&#39;t seeing the spinner, it might just be happening too fast. Try choking your network speed. In the Chrome
browser you can set your network speed to &quot;slow 3g&quot; under the network tab in the developer tools.</p>
</blockquote><p>If you&#39;d like to see a working example of the code so far, have a look <a href="https://codesandbox.io/s/hyperapp-tutorial-step-3-2mmug">here</a></p><h2 id="subscriptions">Subscriptions</h2><p>The last feature we&#39;ll add is to make our app periodically check for new stories matching the filter. There won&#39;t actually
be any because it&#39;s not a real service, but you&#39;ll know it&#39;s happening when you see the spinner pop up every five
seconds. </p><p>However, we want to make it opt-in. That&#39;s what the auto update checkbox at the bottom is for. We need a
property in the state to track wether the box is checked or not. </p><p>Change the <code>AutoUpdate</code> view:</p><pre><code class="language-js">const AutoUpdate = props =&gt; h(&quot;div&quot;, {class: &quot;autoupdate&quot;}, [
&quot;Auto update: &quot;,
h(&quot;input&quot;, {
type: &quot;checkbox&quot;,
checked: props.autoUpdate, // &lt;---
onInput: ToggleAutoUpdate, // &lt;---
})
])</code></pre><p>and implement the <code>ToggleAutoUpdate</code> action:</p><pre><code class="language-js">const ToggleAutoUpdate = state =&gt; ({...state, autoUpdate: !state.autoUpdate})</code></pre><p>Now we&#39;ve got <code>autoUpdate</code> in the state tracking the checkbox. All we need now, is to set up <code>FetchStories</code>
to be dispatched every five seconds when <code>autoUpdate</code> is <code>true</code>.</p><p>Import the <code>interval</code> <em>subscription creator</em>:</p><pre><code class="language-js">import {interval} from &quot;https://unpkg.com/@hyperapp/time?module&quot;</code></pre><p>Add a <code>subscriptions</code> property to your app, with a conditional declaration of <code>interval</code> like this:</p><pre><code class="language-js"> subscriptions: state =&gt; [
state.autoUpdate &amp;&amp; interval(FetchStories, {delay: 5000})
]</code></pre><p>Hyperapp will call <code>subscriptions</code> every time the state changes. If it notices a
new subscription, it will be started, or if one has been removed it will be stopped.</p><p>The options we passed to the <code>interval</code> subscription state that <code>FetchStories</code> should be dispatched every five seconds. It
will start when we check the auto update box, and stop when it is unchecked.</p><blockquote>
<p>As with effects, Hyperapp offers subscriptions for the most common cases, but you
may need to implement your own. Refer to the <a href="">API reference</a>. Again, it is no big deal - just not in scope for this tutorial.</p>
</blockquote><p>If you&#39;d like to see a working example of the final code, have a look <a href="https://codesandbox.io/s/hyperapp-tutorial-step-4-8u9q8">here</a></p><h2 id="conclusion">Conclusion</h2><p>Congratulations on completing this Hyperapp tutorial!</p><p>Along the way you&#39;ve familiarized yourself with
the core concepts: <em>view</em>, <em>state</em>, <em>actions</em>, <em>effects</em> &amp; <em>subscriptions</em>. And that&#39;s really all you need to
build any web application.</p>

915
tutorial.md Normal file
View File

@ -0,0 +1,915 @@
Tutorial
===================================
Welcome! If you're new to Hyperapp, you've found the perfect place to start learning.
The Set-up
-----------------------------------
Together we'll build a simple newsreader-like application. As we do, we'll work
our way through the five core concepts: view, state, actions, effects and subscriptions.
To move things along, let's imagine we've already made a static version of the
app we want to build, with this HTML:
```html
<div id="app" class="container">
<div class="filter">
Filter:
<span class="filter-word">ocean</span>
<button>&#9998;</button>
</div>
<div class="stories">
<ul>
<li class="unread">
<p class="title">The <em>Ocean </em>is Sinking</p>
<p class="author">Kat Stropher</p>
</li>
<li class="reading">
<p class="title"><em>Ocean </em>life is brutal</p>
<p class="author">Surphy McBrah</p>
</li>
<li>
<p class="title">
Family friendly fun at the
<em>ocean </em>exhibit
</p>
<p class="author">Guy Prosales</p>
</li>
</ul>
</div>
<div class="story">
<h1>Ocean life is brutal</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.
</p>
<p class="signature">Surphy McBrah</p>
</div>
<div class="autoupdate">
Auto update:
<input type="checkbox" />
</div>
</div>
```
...and some CSS [here](https://zaceno.github.com/hatut/style.css).
It looks like this:
![Initial static mockup](https://user-images.githubusercontent.com/6243887/73389558-15d97580-42dd-11ea-90fa-f79a2c351fe8.png)
We'll start by making Hyperapp render the HTML for us. Then we will
add dynamic behavior to all the widgets, including text input and
dynamically fetching stories.
First, let's begin with the traditional "Hello World!"
Hello World
------------------------------
Create this html file:
```html
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="https://zaceno.github.com/hatut/style.css">
<script type="module">
// -- IMPORTS --
import {h, app} from "https://unpkg.com/hyperapp?module"
// -- ACTIONS --
// -- VIEWS ---
// -- RUN --
app({
node: document.getElementById("app"),
view: () => h("h1", {}, [
"Hello ",
h("i", {}, "World!")
])
})
</script>
</head>
<body>
<div id="app"></div>
</body>
</html>
```
> The section structure outlined in the comments is not important. It's
> just a suggestion for how to organize the code we'll be
> adding throughout the tutorial.
Open it in a browser, and you'll be greeted with an optimistic **Hello _World!_**.
View
------------------------------------
Let's step through what just happened.
### Virtual Nodes
Hyperapp exports the `app` and `h` functions.
`h` is for creating _virtual nodes_, which is to say: plain javascript objects
which _represent_ DOM nodes.
The result of
```js
h("h1", {}, [
"Hello ",
h("i", {}, "World!")
])
```
is a virtual node, representing
```html
<h1>
Hello
<i>World!</i>
</h1>
```
### Rendering to the DOM
`app` is the function that runs our app. It is called with a single argument - an object
which can take several properties. For now we're just concerned with `view` and `node. `
Hyperapp calls the `view` function which tells it the DOM structure we want, in the form
of virtual nodes. Hyperapp proceeds to create it for us, replacing the node specified in `node`.
To render the HTML we want, change the `view` to:
```js
view: () => h("div", {id: "app", class: "container"}, [
h("div", {class: "filter"}, [
" Filter: ",
h("span", {class: "filter-word"}, "ocean"),
h("button", {}, "\u270E")
]),
h("div", {class: "stories"}, [
h("ul", {}, [
h("li", {class: "unread"}, [
h("p", {class: "title"}, [
"The ",
h("em", {}, "Ocean"),
" is Sinking!"
]),
h("p", {class: "author"}, "Kat Stropher")
]),
h("li", {class: "reading"}, [
h("p", {class: "title"}, [
h("em", {}, "Ocean"),
" life is brutal"
]),
h("p", {class: "author"}, "Surphy McBrah"),
]),
h("li", {}, [
h("p", {class: "title"}, [
"Family friendly fun at the ",
h("em", {}, "ocean"),
" exhibit"
]),
h("p", {class: "author"}, "Guy Prosales")
])
])
]),
h("div", {class: "story"}, [
h("h1", {}, "Ocean life is brutal"),
h("p", {}, `
Lorem ipsum dolor sit amet, consectetur adipiscing
elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat.
`),
h("p", {class: "signature"}, "Surphy McBrah")
]),
h("div", {class: "autoupdate"}, [
"Auto update: ",
h("input", {type: "checkbox"})
])
]),
```
Try it out to confirm that the result matches the screenshot above.
> In many frameworks it is common to write your views/templates
> using syntax that looks like HTML. This is possible with Hyperapp as well.
> [JSX](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx)
> can compile a HTML-like syntax into `h` calls at build-time. If you'd rather
> not use a build system, [htm](https://github.com/developit/htm) does the same at run-time.
>
> In this tutorial we'll stick with `h` to keep it simple and close to the metal.
### Composing the view with reusable functions
The great thing about using plain functions to build up our virtual DOM
is that we can break out repetitive or complicated parts into their own functions.
Add this function (in the "VIEWS" section):
```js
const emphasize = (word, string) =>
string.split(" ").map(x => {
if (x.toLowerCase() === word.toLowerCase()) {
return h("em", {}, x + " ")
} else {
return x + " "
}
})
```
It lets you change this:
```js
...
h("p", {class: "title"}, [
"The ",
h("em", {}, "Ocean"),
" is Sinking!"
]),
...
```
into this:
```js
...
h("p", {class: "title"}, emphasize("ocean",
"The Ocean is Sinking"
))
...
```
Story thumbnails are repeated several times, so encapsulate
them in their own function:
```js
const StoryThumbnail = props => h(
"li",
{class: {
unread: props.unread,
reading: props.reading,
}},
[
h("p", {class: "title"}, emphasize(props.filter, props.title)),
h("p", {class: "author"}, props.author)
]
)
```
> The last example demonstrates a helpful feature of the `class` property. When
> you set it to an object rather than a string, each key with a truthy value
> will become a class in the class list.
Continue by creating functions for each section of the view:
```js
const StoryList = props => h("div", {class: "stories"}, [
h("ul", {}, Object.keys(props.stories).map(id =>
StoryThumbnail({
id,
title: props.stories[id].title,
author: props.stories[id].author,
unread: !props.stories[id].seen,
reading: props.reading === id,
filter: props.filter,
})
))
])
const Filter = props => h("div", {class: "filter"}, [
"Filter:",
h("span", {class: "filter-word"}, props.filter),
h("button", {}, "\u270E")
])
const StoryDetail = props => h("div", {class: "story"}, [
props && h("h1", {}, props.title),
props && h("p", {}, `
Lorem ipsum dolor sit amet, consectetur adipiscing
elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, qui
nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat.
`),
props && h("p", {class: "signature"}, props.author)
])
const AutoUpdate = props => h("div", {class: "autoupdate"}, [
"Auto update: ",
h("input", {type: "checkbox"})
])
const Container = content => h("div", {class: "container"}, content)
```
With those the view can be written as:
```js
view: () => Container([
Filter({
filter: "ocean"
}),
StoryList({
stories: {
"112": {
title: "The Ocean is Sinking",
author: "Kat Stropher",
seen: false,
},
"113": {
title: "Ocean life is brutal",
author: "Surphy McBrah",
seen: true,
},
"114": {
title: "Family friendly fun at the ocean exhibit",
author: "Guy Prosales",
seen: true,
}
},
reading: "113",
filter: "ocean"
}),
StoryDetail({
title: "Ocean life is brutal",
author: "Surphy McBrah",
}),
AutoUpdate(),
])
```
What you see on the page should be exactly the same as before, because we haven't
changed what `view` returns. Using basic functional composition, we were able to make
the code a bit more manageable, and that's the only difference.
State
-------------------------------
With all that view logic broken out in separate functions, `view` is starting to look like
plain _data_. The next step is to fully separate data from the view.
Add an `init` property to your app, with this pure data:
```js
init: {
filter: "ocean",
reading: "113",
stories: {
"112": {
title: "The Ocean is Sinking",
author: "Kat Stropher",
seen: false,
},
"113": {
title: "Ocean life is brutal",
author: "Surphy McBrah",
seen: true,
},
"114": {
title: "Family friendly fun at the ocean exhibit",
author: "Guy Prosales",
seen: true,
}
}
},
```
The value of `init` becomes the app's _state_. Hyperapp calls `view` with the state
as an argument, so it can be reduced to:
```js
view: state => Container([
Filter(state),
StoryList(state),
StoryDetail(state.reading && state.stories[state.reading]),
AutoUpdate(state),
]),
```
Visually, everything is _still_ the same. If you'd like to see a working example of the code so far, have a look [here](https://codesandbox.io/s/hyperapp-tutorial-step-1-gq662).
Actions
---------------------
Now that we know all about rendering views, it's finally time for some _action_!
### Reacting to events in the DOM
The first bit of dynamic behavior we will add is so that when you click
the pencil-button, a text input with the filter word appears.
Add an `onClick` property to the button in the filter view:
```js
const Filter = props => h("div", {class: "filter"}, [
"Filter:",
h("span", {class: "filter-word"}, props.filter),
h("button", { onClick: StartEditingFilter }, "\u270E") // <---
])
```
This makes Hyperapp bind a click-event handler on the button element, so
that when the button is clicked, an action named `StartEditingFilter` is
_dispatched_. Create the action in the "ACTIONS" section:
```js
const StartEditingFilter = state => ({...state, editingFilter: true})
```
Actions are just functions describing transformations of the state.
This action keeps everything in the state the same except for `editingFilter`
which it sets to `true`.
When Hyperapp dispatches an action, it replaces the old state with the new
one calculated using the action. Then the DOM is modified to match what the
view returns for this new state.
When `editingFilter` is true, we want to have a text input instead of a
span with the filter word. We can express this in the `Filter` view using a
ternary operator (`a ? b : c`).
```js
const Filter = props => h("div", {class: "filter"}, [
"Filter:",
props.editingFilter // <---
? h("input", {type: "text", value: props.filter}) // <---
: h("span", {class: "filter-word"}, props.filter),
h("button", { onClick: StartEditingFilter }, "\u270E")
])
```
Now, when you click the pencil button the text input appears. But we still need to add
a way to go back. We need an action to `StopEditingFilter`, and a button to dispatch it.
Add the action:
```js
const StopEditingFilter = state => ({...state, editingFilter: false})
```
and update the `Filter` view again:
```js
const Filter = props => h("div", {class: "filter"}, [
"Filter:",
props.editingFilter
? h("input", {type: "text", value: props.filter})
: h("span", {class: "filter-word"}, props.filter),
props.editingFilter // <---
? h("button", {onClick: StopEditingFilter}, "\u2713")
: h("button", {onClick: StartEditingFilter}, "\u270E"), // <---
])
```
When you click the pencil button, it is replaced with a check-mark button that can take you back to the first state.
![Filter in edit mode](https://user-images.githubusercontent.com/6243887/73389562-1a059300-42dd-11ea-80ea-631c999d5f62.png)
### Capturing event-data in actions
The next step is to use the input for editing the filter word. Whatever we
type in the box should be emphasized in the story-list.
Update the `Filter` view yet again:
```js
const Filter = props => h("div", {class: "filter"}, [
"Filter:",
props.editingFilter
? h("input", {
type: "text",
value: props.filter,
onInput: SetFilter, // <----
})
: h("span", {class: "filter-word"}, props.filter),
props.editingFilter
? h("button", {onClick: StopEditingFilter}, "\u2713")
: h("button", {onClick: StartEditingFilter}, "\u270E"),
])
```
This will dispatch the `SetFilter` action everytime someone types in the input. Implement the action like this:
```js
const SetFilter = (state, event) => ({...state, filter: event.target.value})
```
The second argument to an action is known as the _payload_. Actions
dispatched in response to an events on DOM elements receive the [event object](https://developer.mozilla.org/en-US/docs/Web/API/Event) for a payload. `event.target` refers to the input element in the DOM, and
`event.target.value` refers to the current value entered into it.
Now see what happens when you erase "ocean" and type "friendly" instead:
![filtering other words](https://user-images.githubusercontent.com/6243887/73389567-1d991a00-42dd-11ea-9bf1-b1fc6b85b635.png)
### Actions with custom payloads
Next up: selecting stories by clicking them in the list.
The following action sets the `reading` property in the state to a story-id, which amounts to "selecting" the story:
```js
const SelectStory = (state, id) => ({...state, reading: id})
```
It has a payload, but it's not an event object. It's a custom value telling us which
story was clicked. How are actions dispatched with custom payloads?  Like this:
```js
const StoryThumbnail = props => h(
"li",
{
onClick: [SelectStory, props.id], // <----
class: {
unread: props.unread,
reading: props.reading,
}
},
[
h("p", {class: "title"}, emphasize(props.filter, props.title)),
h("p", {class: "author"}, props.author)
]
)
```
Instead of just specifying the action, we give a length-2 array with the action first and the custom payload second.
Selecting stories works now, but the feature is not quite done. When a story is selected,
we need to set its `seen` property to `true`, so we can highlight which stories the user has yet to read. Update the `SelectStory` action:
```js
const SelectStory = (state, id) => ({
...state, // keep all state the same, except for the following:
reading: id,
stories: {
...state.stories, //keep stories the same, except for:
[id]: {
...state.stories[id], //keep this story the same, except for:
seen: true,
}
}
})
```
Now, when you select a blue-edged story it turns yellow because it is selected, and when you select something else,
the edge turns gray to indicate you've read the story.
![all stories read](https://user-images.githubusercontent.com/6243887/73389573-20940a80-42dd-11ea-9b26-ebdad474b169.png)
### Payload filters
There's one little thing we should fix about `SetFilter`. See how it's dependent on the complex `event` object?
It would be easier to test and reuse if it were simply:
```js
const SetFilter = (state, word) => ({...state, filter: word})
```
But we don't know the word beforehand, so how can we set it as a custom payload? Change the `Filter` view
again (last time - I promise!):
```js
const Filter = props => h("div", {class: "filter"}, [
"Filter:",
props.editingFilter
? h("input", {
type: "text",
value: props.filter,
onInput: [SetFilter, event => event.target.value], // <----
})
: h("span", {class: "filter-word"}, props.filter),
props.editingFilter
? h("button", {onClick: StopEditingFilter}, "\u2713")
: h("button", {onClick: StartEditingFilter}, "\u270E"),
])
```
When we give a _function_ as the custom payload, Hyperapp considers it a _payload filter_ and passes the default
payload through it, providing the returned value as payload to the action.
> Payload filters are also useful when you need a payload that is a combination of custom data and event data
If you'd like to see a working example of the code so far, have a look [here](https://codesandbox.io/s/hyperapp-tutorial-step-2-5yv34).
Effects
----------------------------
Until now, the list of stories has been defined in the state and doesn't change. What we really want is
for stories matching the filter to be dynamically loaded. When we click the check-mark button
(indicating we are done editing the filter), we want to query an API and display the stories it responds with.
### Actions can return effects
Add this import (to the "IMPORTS" section):
```js
import {Http} from "https://unpkg.com/hyperapp-fx@next?module"
```
Use the imported `Http` in the `StopEditingFilter` action like this:
```js
const StopEditingFilter = state => [
{
...state,
editingFilter: false,
},
Http({ // <---
url: `https://zaceno.github.io/hatut/data/${state.filter.toLowerCase()}.json`, // <---
response: "json", // <---
action: GotStories, // <---
})
]
```
The call to `Http(...)` does _not_ immediately execute the API request. `Http` is an _effect creator_. It returns
an _effect_ bound to the options we provided.
When Hyperapp sees an action return an array, it takes the first element of the array to be the new state, and the rest to
be _effects_. Effects are executed by Hyperapp as part of processing the action's return value.
> Hyperapp provides effect creators for many common situations. If you've got an unusual case or are working
> with less common APIs you may need to implement your own effects. Don't worry - it's easy! See the
> [API reference](./ref.md) for more information.
### Effects can dispatch actions
One of the options we passed to `Http` was `action: GotStories`. The way this effect works is that when the response comes
back from the api, an action named `GotStories` (yet to be implemented) will be dispatched, with the response body as the payload.
The response body is in json, but the payload will be a javascript object, thanks to the parsing hint `response: "json"`. It will look like this (although the details depend on your filter of course):
```js
{
"112": {
title: "The Ocean is Sinking",
author: "Kat Stropher",
},
"113": {
title: "Ocean life is brutal",
author: "Surphy McBrah",
},
"114": {
title: "Family friendly fun at the ocean exhibit",
author: "Guy Prosales",
}
}
```
The job of `GotStories` is to load this data into the state, in place of the stories we already have there. As it
does, it should take care to remember which story was selected, and which stories we have seen, if they were already
in the previous state. This will be our most complex action yet, and it could look like this:
```js
const GotStories = (state, response) => {
const stories = {}
Object.keys(response).forEach(id => {
stories[id] = {...response[id], seen: false}
if (state.stories[id] && state.stories[id].seen) {
stories[id].seen = true
}
})
const reading = stories[state.reading] ? state.reading :  null
return {
...state,
stories,
reading,
}
}
```
Try it out! Enter "life" in the filter input. When you click the check-mark button some new
stories are loaded all with blue edges except for "Ocean life is brutal" because it is
still selected.
![loaded other stories](https://user-images.githubusercontent.com/6243887/73389577-24279180-42dd-11ea-8b4a-b231f1c811c8.png)
### Running effects on initialization
The next obvious step is to load the _initial_ stories from the API as well. Change init to this:
```js
init: [
{
editingFilter: false,
autoUpdate: false,
filter: "ocean",
reading: null,
stories: {}, // <---
},
Http({ // <---
url: `https://zaceno.github.io/hatut/data/ocean.json`, // <---
response: 'json', // <---
action: GotStories, // <---
})
],
```
Hyperapp treats the init-value the same way as it treats return values from actions. By adding the `Http` effect
in `init`, the app will fire the API request immediately, so we don't need the stories in the state from the start.
![stories loaded from start](https://user-images.githubusercontent.com/6243887/73389586-2a1d7280-42dd-11ea-8642-f994c028a74f.png)
### Tracking state for asynchronous effects
If we could display a spinner while we wait for stories to load, it would make for a smoother user experience. To
do that, we will need a new state property to tell us if we're waiting for a repsonse - and
consequently wether or not to render the spinner.
Create this action:
```js
const FetchStories = state => [
{...state, fetching: true},
Http({
url: `https://zaceno.github.io/hatut/data/${state.filter.toLowerCase()}.json`,
response: 'json',
action: GotStories,
})
]
```
Instead of dispatching this action, we will use it to simplify `StopEditingFilter`:
```js
const StopEditingFilter = state => FetchStories({...state, editingFilter: false})
```
... and `init` as well:
```js
init: FetchStories({
editingFilter: false,
autoUpdate: false,
filter: "ocean",
reading: null,
stories: {},
}),
```
Now, when `StopEditingFilter` is dispatched, _and_ at initialization, the API call goes out and the
`fetching` prop is set to `true`. Also, notice how we refactored out the repetitive use of `Http`.
We also need to set `fetching: false` in `GotStories`:
```js
const GotStories = (state, response) => {
const stories = {}
Object.keys(response).forEach(id => {
stories[id] = {...response[id], seen: false}
if (state.stories[id] && state.stories[id].seen) {
stories[id].seen = true
}
})
const reading = stories[state.reading] ? state.reading :  null
return {
...state,
stories,
reading,
fetching: false, // <---
}
}
```
With this, we know that when `fetching` is `true` we are waiting for a response, and should display
the spinner in the `StoryList` view:
```js
const StoryList = props => h("div", {class: "stories"}, [
props.fetching && h("div", {class: "loadscreen"}, [ // <---
h("div", {class: "spinner"}) // <---
]), // <---
h("ul", {}, Object.keys(props.stories).map(id =>
StoryThumbnail({
id,
title: props.stories[id].title,
author: props.stories[id].author,
unread: !props.stories[id].seen,
reading: props.reading === id,
filter: props.filter
})
))
])
```
When the app loads, and when you change the filter, you should see the spinner appear until the stories are loaded.
![loading spinner](https://user-images.githubusercontent.com/6243887/73389594-2db0f980-42dd-11ea-8bf8-95b96e7337b1.png)
> If you aren't seeing the spinner, it might just be happening too fast. Try choking your network speed. In the Chrome
> browser you can set your network speed to "slow 3g" under the network tab in the developer tools.
If you'd like to see a working example of the code so far, have a look [here](https://codesandbox.io/s/hyperapp-tutorial-step-3-2mmug).
Subscriptions
-------------------------------------------------------------------
The last feature we'll add is to make our app periodically check for new stories matching the filter. There won't actually
be any because it's not a real service, but you'll know it's happening when you see the spinner pop up every five
seconds.
However, we want to make it opt-in. That's what the auto update checkbox at the bottom is for. We need a
property in the state to track wether the box is checked or not.
Change the `AutoUpdate` view:
```js
const AutoUpdate = props => h("div", {class: "autoupdate"}, [
"Auto update: ",
h("input", {
type: "checkbox",
checked: props.autoUpdate, // <---
onInput: ToggleAutoUpdate, // <---
})
])
```
and implement the `ToggleAutoUpdate` action:
```js
const ToggleAutoUpdate = state => ({...state, autoUpdate: !state.autoUpdate})
```
Now we've got `autoUpdate` in the state tracking the checkbox. All we need now, is to set up `FetchStories`
to be dispatched every five seconds when `autoUpdate` is `true`.
Import the `interval` _subscription creator_:
```js
import {interval} from "https://unpkg.com/@hyperapp/time?module"
```
Add a `subscriptions` property to your app, with a conditional declaration of `interval` like this:
```js
subscriptions: state => [
state.autoUpdate && interval(FetchStories, {delay: 5000})
]
```
Hyperapp will call `subscriptions` every time the state changes. If it notices a
new subscription, it will be started, or if one has been removed it will be stopped.
The options we passed to the `interval` subscription state that `FetchStories` should be dispatched every five seconds. It
will start when we check the auto update box, and stop when it is unchecked.
![auto updating](https://user-images.githubusercontent.com/6243887/73389603-3275ad80-42dd-11ea-9270-bc8be471db8b.png)
> As with effects, Hyperapp offers subscriptions for the most common cases, but you
> may need to implement your own. Refer to the [API reference](./ref.md). Again,
> it is no big deal - just not in scope for this tutorial.
If you'd like to see a working example of the final code, have a look [here](https://codesandbox.io/s/hyperapp-tutorial-step-4-8u9q8).
Conclusion
------------------
Congratulations on completing this Hyperapp tutorial!
Along the way you've familiarized yourself with
the core concepts: _view_, _state_, _actions_, _effects_ & _subscriptions_. And that's really all you need to
build any web application.
So now, go build your dream app, or browse our [Examples](./examples.md) for more
inspiration.

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="102px" height="100px" viewBox="0 0 102 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>WriteWhatNotHow</title>
<desc>Created with Sketch.</desc>
<defs>
<rect id="path-1" x="0" y="49" width="51" height="51"></rect>
<mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="51" height="51" fill="white">
<use xlink:href="#path-1"></use>
</mask>
</defs>
<g id="WriteWhatNotHow" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<use id="Rectangle" stroke="#1661EE" mask="url(#mask-2)" stroke-width="2" stroke-dasharray="1,2" xlink:href="#path-1"></use>
<rect id="Rectangle" fill="#A5C4FF" x="25" y="26" width="51" height="51"></rect>
<rect id="Rectangle" fill="#1661EE" x="51" y="0" width="51" height="51"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB