Socket
Socket
Sign inDemoInstall

hyperapp

Package Overview
Dependencies
Maintainers
1
Versions
129
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hyperapp - npm Package Compare versions

Comparing version 2.0.0-beta.22 to 2.0.0-beta.24

2

dist/hyperapp.js

@@ -1,2 +0,2 @@

var hyperapp=function(n){"use strict";var e={},r=[],t=r.map,o=Array.isArray,l=requestAnimationFrame||setTimeout,i=function(n){var e="";if("string"==typeof n)return n;if(o(n)&&n.length>0)for(var r,t=0;t<n.length;t++)""!==(r=i(n[t]))&&(e+=(e&&" ")+r);else for(var t in n)n[t]&&(e+=(e&&" ")+t);return e},u=function(n,e){var r={};for(var t in n)r[t]=n[t];for(var t in e)r[t]=e[t];return r},f=function(n){return n.reduce(function(n,e){return n.concat(e&&!0!==e?"function"==typeof e[0]?[e]:f(e):0)},r)},a=function(n,e){return o(n)&&o(e)&&n[0]===e[0]&&"function"==typeof n[0]},c=function(n,e){if(n!==e)for(var r in u(n,e)){if(n[r]!==e[r]&&!a(n[r],e[r]))return!0;e[r]=n[r]}},s=function(n,e,r,t,o,l){if("key"===e);else if("style"===e)for(var f in u(r,t))r=null==t||null==t[f]?"":t[f],"-"===f[0]?n[e].setProperty(f,r):n[e][f]=r;else"o"===e[0]&&"n"===e[1]?((n.actions||(n.actions={}))[e=e.slice(2).toLowerCase()]=t)?r||n.addEventListener(e,o):n.removeEventListener(e,o):!l&&"list"!==e&&e in n?n[e]=null==t?"":t:null==t||!1===t||"class"===e&&!(t=i(t))?n.removeAttribute(e):n.setAttribute(e,t)},d=function(n,e,r){var t=3===n.type?document.createTextNode(n.name):(r=r||"svg"===n.name)?document.createElementNS("http://www.w3.org/2000/svg",n.name):document.createElement(n.name),o=n.props;for(var l in o)s(t,l,null,o[l],e,r);for(var i=0,u=n.children.length;i<u;i++)t.appendChild(d(n.children[i]=y(n.children[i]),e,r));return n.node=t},p=function(n){return null==n?null:n.key},v=function(n,e,r,t,o,l){if(r===t);else if(null!=r&&3===r.type&&3===t.type)r.name!==t.name&&(e.nodeValue=t.name);else if(null==r||r.name!==t.name)e=n.insertBefore(d(t=y(t),o,l),e),null!=r&&n.removeChild(r.node);else{var i,f,a,c,h=r.props,m=t.props,g=r.children,w=t.children,z=0,C=0,k=g.length-1,A=w.length-1;for(var L in l=l||"svg"===t.name,u(h,m))("value"===L||"selected"===L||"checked"===L?e[L]:h[L])!==m[L]&&s(e,L,h[L],m[L],o,l);for(;C<=A&&z<=k&&null!=(a=p(g[z]))&&a===p(w[C]);)v(e,g[z].node,g[z],w[C]=y(w[C++],g[z++]),o,l);for(;C<=A&&z<=k&&null!=(a=p(g[k]))&&a===p(w[A]);)v(e,g[k].node,g[k],w[A]=y(w[A--],g[k--]),o,l);if(z>k)for(;C<=A;)e.insertBefore(d(w[C]=y(w[C++]),o,l),(f=g[z])&&f.node);else if(C>A)for(;z<=k;)e.removeChild(g[z++].node);else{L=z;for(var N={},b={};L<=k;L++)null!=(a=g[L].key)&&(N[a]=g[L]);for(;C<=A;)a=p(f=g[z]),c=p(w[C]=y(w[C],f)),b[a]||null!=c&&c===p(g[z+1])?(null==a&&e.removeChild(f.node),z++):null==c||1===r.type?(null==a&&(v(e,f&&f.node,f,w[C],o,l),C++),z++):(a===c?(v(e,f.node,f,w[C],o,l),b[c]=!0,z++):null!=(i=N[c])?(v(e,e.insertBefore(i.node,f&&f.node),i,w[C],o,l),b[c]=!0):v(e,f&&f.node,null,w[C],o,l),C++);for(;z<=k;)null==p(f=g[z++])&&e.removeChild(f.node);for(var L in N)null==b[L]&&e.removeChild(N[L].node)}}return t.node=e},y=function(n,e){return 2===n.type?((!e||function(n,e){for(var r in n)if(n[r]!==e[r])return!0;for(var r in e)if(n[r]!==e[r])return!0}(e.lazy,n.lazy))&&((e=n.lazy.view(n.lazy)).lazy=n.lazy),e):n},h=function(n,e,r,t,o,l){return{name:n,props:e,children:r,node:t,type:l,key:o}},m=function(n,t){return h(n,e,r,t,null,3)},g=function(n){return 3===n.nodeType?m(n.nodeValue,n):h(n.nodeName.toLowerCase(),e,t.call(n.childNodes,g),n,null,1)};return n.Lazy=function(n){return{lazy:n,type:2}},n.app=function(n,e){var r={},t=!1,i=n.view,u=n.node,a=u&&g(u),s=n.subscriptions,d=[],p=function(n){h(this.actions[n.type],n)},y=function(n){return r!==n&&(r=n,s&&(d=function(n,e,r){for(var t,o,l=0,i=[];l<n.length||l<e.length;l++)t=n[l],o=e[l],i.push(o?!t||o[0]!==t[0]||c(o[1],t[1])?[o[0],o[1],o[0](r,o[1]),t&&t[2]()]:t:t&&t[2]());return i}(d,f([s(r)]),h)),i&&!t&&l(w,t=!0)),r},h=(e||function(n){return n})(function(n,e,t){return"function"==typeof n?h(n(r,e),t||e):o(n)?"function"==typeof n[0]?h(n[0],"function"==typeof(n=n[1])?n(e):n,e):(f(n.slice(1)).map(function(n){n&&n[0](h,n[1],e)},y(n[0])),r):y(n)}),w=function(){t=!1,u=v(u.parentNode,u,a,a="string"==typeof(a=i(r))?m(a):a,p)};h(n.init)},n.h=function(n,r){for(var t,l=[],i=[],u=arguments.length;u-- >2;)l.push(arguments[u]);for(;l.length>0;)if(o(t=l.pop()))for(u=t.length;u-- >0;)l.push(t[u]);else!1===t||!0===t||null==t||i.push("object"==typeof t?t:m(t));return r=r||e,"function"==typeof n?n(r,i):h(n,r,i,null,r.key)},n}({});
var hyperapp=function(e){"use strict";var n={},r=[],t=r.map,o=Array.isArray,i=requestAnimationFrame||setTimeout,l=function(e){var n="";if("string"==typeof e)return e;if(o(e)&&e.length>0)for(var r,t=0;t<e.length;t++)""!==(r=l(e[t]))&&(n+=(n&&" ")+r);else for(var t in e)e[t]&&(n+=(n&&" ")+t);return n},u=function(e,n){var r={};for(var t in e)r[t]=e[t];for(var t in n)r[t]=n[t];return r},f=function(e){return e.reduce(function(e,n){return e.concat(n&&!0!==n?"function"==typeof n[0]?[n]:f(n):0)},r)},a=function(e,n){return o(e)&&o(n)&&e[0]===n[0]&&"function"==typeof e[0]},c=function(e,n){if(e!==n)for(var r in u(e,n)){if(e[r]!==n[r]&&!a(e[r],n[r]))return!0;n[r]=e[r]}},s=function(e,n,r,t,o,i){if("key"===n);else if("style"===n)for(var f in u(r,t))r=null==t||null==t[f]?"":t[f],"-"===f[0]?e[n].setProperty(f,r):e[n][f]=r;else"o"===n[0]&&"n"===n[1]?((e.actions||(e.actions={}))[n=n.slice(2).toLowerCase()]=t)?r||e.addEventListener(n,o):e.removeEventListener(n,o):!i&&"list"!==n&&n in e?e[n]=null==t?"":t:null==t||!1===t||"class"===n&&!(t=l(t))?e.removeAttribute(n):e.setAttribute(n,t)},d=function(e,n,r){var t=e.props,o=3===e.type?document.createTextNode(e.name):(r=r||"svg"===e.name)?document.createElementNS("http://www.w3.org/2000/svg",e.name,{is:t.is}):document.createElement(e.name,{is:t.is});for(var i in t)s(o,i,null,t[i],n,r);for(var l=0,u=e.children.length;l<u;l++)o.appendChild(d(e.children[l]=y(e.children[l]),n,r));return e.node=o},p=function(e){return null==e?null:e.key},v=function(e,n,r,t,o,i){if(r===t);else if(null!=r&&3===r.type&&3===t.type)r.name!==t.name&&(n.nodeValue=t.name);else if(null==r||r.name!==t.name)n=e.insertBefore(d(t=y(t),o,i),n),null!=r&&e.removeChild(r.node);else{var l,f,a,c,h=r.props,m=t.props,g=r.children,w=t.children,z=0,C=0,k=g.length-1,A=w.length-1;for(var L in i=i||"svg"===t.name,u(h,m))("value"===L||"selected"===L||"checked"===L?n[L]:h[L])!==m[L]&&s(n,L,h[L],m[L],o,i);for(;C<=A&&z<=k&&null!=(a=p(g[z]))&&a===p(w[C]);)v(n,g[z].node,g[z],w[C]=y(w[C++],g[z++]),o,i);for(;C<=A&&z<=k&&null!=(a=p(g[k]))&&a===p(w[A]);)v(n,g[k].node,g[k],w[A]=y(w[A--],g[k--]),o,i);if(z>k)for(;C<=A;)n.insertBefore(d(w[C]=y(w[C++]),o,i),(f=g[z])&&f.node);else if(C>A)for(;z<=k;)n.removeChild(g[z++].node);else{L=z;for(var N={},b={};L<=k;L++)null!=(a=g[L].key)&&(N[a]=g[L]);for(;C<=A;)a=p(f=g[z]),c=p(w[C]=y(w[C],f)),b[a]||null!=c&&c===p(g[z+1])?(null==a&&n.removeChild(f.node),z++):null==c||1===r.type?(null==a&&(v(n,f&&f.node,f,w[C],o,i),C++),z++):(a===c?(v(n,f.node,f,w[C],o,i),b[c]=!0,z++):null!=(l=N[c])?(v(n,n.insertBefore(l.node,f&&f.node),l,w[C],o,i),b[c]=!0):v(n,f&&f.node,null,w[C],o,i),C++);for(;z<=k;)null==p(f=g[z++])&&n.removeChild(f.node);for(var L in N)null==b[L]&&n.removeChild(N[L].node)}}return t.node=n},y=function(e,n){return 2===e.type?((!n||function(e,n){for(var r in e)if(e[r]!==n[r])return!0;for(var r in n)if(e[r]!==n[r])return!0}(n.lazy,e.lazy))&&((n=e.lazy.view(e.lazy)).lazy=e.lazy),n):e},h=function(e,n,r,t,o,i){return{name:e,props:n,children:r,node:t,type:i,key:o}},m=function(e,t){return h(e,n,r,t,void 0,3)},g=function(e){return 3===e.nodeType?m(e.nodeValue,e):h(e.nodeName.toLowerCase(),n,t.call(e.childNodes,g),e,void 0,1)};return e.Lazy=function(e){return{lazy:e,type:2}},e.app=function(e){var n={},r=!1,t=e.view,l=e.node,u=l&&g(l),a=e.subscriptions,s=[],d=function(e){y(this.actions[e.type],e)},p=function(e){return n!==e&&(n=e,a&&(s=function(e,n,r){for(var t,o,i=0,l=[];i<e.length||i<n.length;i++)t=e[i],o=n[i],l.push(o?!t||o[0]!==t[0]||c(o[1],t[1])?[o[0],o[1],o[0](r,o[1]),t&&t[2]()]:t:t&&t[2]());return l}(s,f([a(n)]),y)),t&&!r&&i(h,r=!0)),n},y=(e.middleware||function(e){return e})(function(e,r){return"function"==typeof e?y(e(n,r)):o(e)?"function"==typeof e[0]?y(e[0],"function"==typeof e[1]?e[1](r):e[1]):(f(e.slice(1)).map(function(e){e&&e[0](y,e[1])},p(e[0])),n):p(e)}),h=function(){r=!1,l=v(l.parentNode,l,u,u="string"==typeof(u=t(n))?m(u):u,d)};y(e.init)},e.h=function(e,r){for(var t,i=[],l=[],u=arguments.length;u-- >2;)i.push(arguments[u]);for(;i.length>0;)if(o(t=i.pop()))for(u=t.length;u-- >0;)i.push(t[u]);else!1===t||!0===t||null==t||l.push("object"==typeof t?t:m(t));return r=r||n,"function"==typeof e?e(r,l):h(e,r,l,void 0,r.key)},e}({});
//# sourceMappingURL=hyperapp.js.map
{
"name": "hyperapp",
"description": "JavaScript micro-framework for building web interfaces.",
"version": "2.0.0-beta.22",
"description": "The tiny framework for building web interfaces.",
"version": "2.0.0-beta.24",
"main": "src/index.js",
"module": "src/index.js",
"unpkg": "src/index.js",
"browser": "src/index.js",
"license": "MIT",

@@ -23,3 +25,3 @@ "repository": "jorgebucaran/hyperapp",

"scripts": {
"test": "exit 0",
"test": "nyc -i esm -r lcov testmatrix test/*.test.js && nyc report",
"build": "export dir=${pkg:+lib/$pkg/} pkg=$npm_package_name$pkg; npm run bundle && npm run minify",

@@ -33,5 +35,9 @@ "bundle": "rollup -i ${dir}$npm_package_module -o ${dir}dist/$pkg.js --no-esModule -mf iife -n $pkg",

"devDependencies": {
"rollup": "*",
"terser": "^4.0.0"
"esm": "^3.2.25",
"nyc": "^14.1.1",
"testmatrix": "^0.1.2",
"jsdom": "15.1.1",
"rollup": "^1.70.0",
"terser": "^4.1.2"
}
}

@@ -1,60 +0,21 @@

# Hyperapp
# Hyperapp [![npm](https://img.shields.io/npm/v/hyperapp.svg?label=&color=0080FF)](https://github.com/jorgebucaran/hyperapp/releases/latest)
[![Travis CI](https://img.shields.io/travis/jorgebucaran/hyperapp/master.svg)](https://travis-ci.org/jorgebucaran/hyperapp)
[![npm](https://img.shields.io/npm/v/hyperapp.svg)](https://www.npmjs.org/package/hyperapp)
[![Slack](https://hyperappjs.herokuapp.com/badge.svg)](https://hyperappjs.herokuapp.com "Join us")
> The tiny framework for building web interfaces.
Hyperapp is a JavaScript micro-framework for building web interfaces.
- **Do more with less**—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.
- **Write what, not how**—With a declarative syntax that's easy to read and natural to write, Hyperapp is your tool of choice to develop purely functional, feature-rich, browser-based applications.
- **Hypercharged**—Hyperapp is a modern VDOM engine, state management solution, and application design pattern all-in-one. Once you learn to use it, there'll be no end to what you can do.
- **Do more with less**—We have aggressively minimized the concepts you need to learn to be productive right now. Views, actions, effects and subscriptions are all pretty easy to get to grips with and work together seamlessly.
- **Write what, not how**—Create dynamic UIs, run side effects, and subscribe to event streams in the same declarative style. Hyperapp is your tool of choice to develop purely functional, browser-based applications.
- **Batteries-included**—Hyperapp includes state management and a modern Virtual DOM engine that supports keyed updates, components & view memoization out of the box—you'll never go back to DOM traversal and manipulation.
To learn more, go to <https://hyperapp.dev> for documentation, guides, and examples.
> [Check out the examples](#examples) and [follow Hyperapp](https://twitter.com/hyperappjs) on Twitter for news and updates. Love Hyperapp? Please [support me](https://patreon.com/jorgebucaran) on Patreon. Not comfortable with a recurring pledge? I accept one-time donations via [PayPal](https://www.paypal.me/jorgebucaran) too. Thank you. ❤️
## Quickstart
## Table of Contents
Install Hyperapp with npm or Yarn:
- [Installation](#installation)
- [**Quickstart**](#quickstart)
- [Help, I'm stuck!](#help-im-stuck)
- [Fundamentals](#fundamentals)
- [Initializing the state](#initializing-the-state)
- [Rendering a page](#rendering-a-page)
- [Dispatching actions](#dispatching-actions)
- [Handling text input](#handling-text-input)
- [Putting it all together](#putting-it-all-together)
- [Subscriptions](#subscriptions)
- [Controlling time](#controlling-time)
- [Listening to global events](#listening-to-global-events)
- [Implementing your own subscriptions](#implementing-your-own-subscriptions)
- [Effects](#effects)
- [Talking to servers](#talking-to-servers)
- [Manipulating the DOM](#manipulating-the-dom)
- [Generating random numbers](#generating-random-numbers)
- [Implementing your own effects](#implementing-your-own-effects)
- [HTML Attributes](#html-attributes)
- [Techniques](#techniques)
- [Testing]
- [Hydration]
- [Navigation]
- [Working with Forms](#working-with-forms)
- [Using external libraries](#using-external-libraries)
- [Animating elements]
- [Optimization](#optimization)
- [Keys]
- [Lazy views]
- [Examples](#examples)
- [Ecosystem](#ecosystem)
- [License](#license)
```console
npm i hyperapp
```
## Installation
Then with a module bundler like [Parcel](https://parceljs.org) or [Webpack](https://webpack.js.org) import it in your application and get right down to business.
Install the latest version of Hyperapp with a package manager. We recommend using npm or Yarn to manage your front-end dependencies and keep them up-to-date.
<pre>
npm i hyperapp@<em>beta</em>
</pre>
Then with a module bundler like [Parcel](https://parceljs.org) or [Webpack](https://webpack.js.org) import Hyperapp in your application and get right down to business.
```js

@@ -64,3 +25,3 @@ import { h, app } from "hyperapp"

Don't want to set up a build step? Import Hyperapp in a `<script>` tag as a module. Don't worry, modules are supported in all evergreen, self-updating desktop and mobile browsers.
Don't want to set up a build step? Import Hyperapp in a `<script>` tag as a module. Don't worry; modules are supported in all evergreen, self-updating desktop, and mobile browsers.

@@ -73,10 +34,4 @@ ```html

Want to get a sense of what Hyperapp is like without installing anything? Try it in [this code playground](#).
Here's the first example to get you started: a counter that can go up or down. You can try it online [here](https://codesandbox.io/s/hyperapp-playground-fwjlo).
## Quickstart
In this section, we'll walk you through your first example: a counter that can go up or down. It won't be a real-world application, but you'll get a taste of how Hyperapp works. You'll learn how to initialize your application state, wire actions to DOM events, and render HTML on the page. Before we sign off, we'll even set up a build step and a local development server using a JavaScript module bundler.
First, create a new `index.html` file and paste the following code in it.
```html

@@ -90,3 +45,3 @@ <!DOCTYPE html>

app({
init: () => 0,
init: 0,
view: state =>

@@ -108,1066 +63,20 @@ h("div", {}, [

We want to take over the empty `<div>` in the document body and replace it with our view. Maybe your program is within a broader application, in a sidebar widget and surrounded by other elements. That's fine too. Hyperapp gives you absolute control over where the root element of your application is rendered in the DOM.
The app starts off with `init` as the initial state. Our code doesn'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.
The application starts by dispatching the `init` action to initialize the state. Our code does not explicitly maintain any state. Instead, we define actions to transform it and a view to visualize it. The view returns a representation of the DOM known as a virtual DOM, and Hyperapp updates the actual DOM to match it.
Now it'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?
Here's what the virtual DOM looks like, abridged for clarity.
```js
{
name: "div",
props: {},
children: [
{
name: "h1",
props: {},
children: [0]
},
{
name: "button",
props: {},
children: ["-"]
},
{
name: "button",
props: {},
children: ["+"]
}
]
}
```
We use Hyperapp's `h` function to create virtual DOM nodes. It takes three arguments: a string that specifies the name of the node; `div`, `h1`, `button`, etc., an object of HTML or SVG properties, and an array of child nodes. Describing HTML trees using functions, also known as hyperscript, is a common idea in virtual DOM implementations; the virtual DOM object specification may vary between libraries, but the function's signature stays the same.
Another way of creating virtual DOM nodes is using [JSX](https://facebook.github.io/jsx). JSX is an embeddable XML-like syntax language extension that lets you write HTML tags interspersed with JavaScript. It's syntactic sugar for pure, nested `h` function calls. The trade-off is that we need to compile it to standard JavaScript using a specialized tool before we can run the application.
If you are tagging along, create an `index.js` file and paste the following code in it.
```jsx
import { h, app } from "hyperapp"
app({
init: () => 0,
view: state => (
<div>
<h1>{state}</h1>
<button onclick={state => state - 1}>-</button>
<button onclick={state => state + 1}>+</button>
</div>
),
node: document.getElementById("app")
})
```
We'll use [Babel](https://babeljs.io) to translate JSX to `h` function calls. First, install [`@babel/core`](https://www.npmjs.com/package/@babel/core) and [`@babel/plugin-transform-react-jsx`](https://www.npmjs.com/package/@babel/plugin-transform-react-jsx). One is the compiler, the other is the JSX to JavaScript plugin. Then, add the following configuration to your `.babelrc` file, creating one if you haven't already.
```json
{
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "h"
}
]
]
}
```
Fair warning, if you see JSX used in this documentation, it's purely a stylistic choice. If you don't want to set up a build step, there are compilation-free options such as [@hyperapp/html](https://github.com/hyperapp/html), [htmlo], and [htm](https://github.com/developit/htm). Try them all to find out which one works best for you.
Now, open the `index.html` file you created before and modify it like so.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script defer src="index.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
```
Finally, let's put it all together with a module bundler. Bundlers take JavaScript modules (or fonts, images, stylesheets), and combine them into one or a few files optimized for the browser. For this example, we choose [Parcel](https://parceljs.org) because of its low barrier to entry. If you prefer [Webpack](https://webpack.js.org) or [Rollup](http://rollupjs.org), refer to their documentation for usage details.
Once you've installed [`parcel`](https://www.npmjs.com/package/parcel-bundler), go ahead and start the local development server. It automatically rebuilds your app as you change files, reloading the browser window for you.
```console
$ parcel index.html
```
...and you should be up and running. You did it!
If something isn't working as expected you can always [look at] the source online to see how we did it. Experiment with the code. Spend some time thinking about how the view reacts to changes in the state. Can we add a button that resets the counter back to zero? How about disabling the decrement button if the state is less than one? Let's work on that next.
Previously, we defined actions inside the view function. This is inefficient, as it creates a new function every time Hyperapp calls the view. Anonymous functions are also awkward to debug since they don't have a name. A good rule of thumb is to create a function for every action in your program. Don't hold back, they're cheap!
```jsx
const Reset = () => 0
const Decrement = state => state - 1
const Increment = state => state + 1
```
An action can be any function that takes the application state as the first argument and returns a new state. Notice that `Reset` doesn't need the state argument to reset the counter, so we ignore it. Actions can also receive a payload, but let's not get ahead of ourselves.
To indicate the user can't interact with the button, we'll assign a boolean value to the element's `disabled` attribute as follows.
<!-- prettier-ignore -->
```jsx
<button onclick={Decrement} disabled={state === 0}>-</button>
```
That should be all. Here's how our program looks now.
<!-- prettier-ignore -->
```jsx
import { h, app } from "hyperapp"
const Reset = () => 0
const Decrement = state => state - 1
const Increment = state => state + 1
app({
init: Reset,
view: state => (
<div>
<h1>{state}</h1>
<button onclick={Reset}>Reset</button>
<button onclick={Decrement} disabled={state === 0}>-</button>
<button onclick={Increment}>+</button>
</div>
),
node: document.getElementById("app")
})
```
There's still a lot of ground to cover, but we're off to a great start! In the following sections, we'll learn how to use actions to their full extent, handle text input, submit forms, create side-effects, subscribe to global events, talk to servers, manipulate the DOM, and more. {{By the end, I hope you will not only be able to create great applications in Hyperapp, but also understand the core ideas and patterns that make Hyperapp nice to use}}.
## Help, I'm stuck!
We all get stuck sometimes. If you've hit a stumbling block hop on the [Hyperapp Slack Room](https://hyperappjs.herokuapp.com) to get help quickly, and if you don't receive an answer, or if you remain stuck, please file an issue, and we'll try to help you out.
We love to talk JavaScript and Hyperapp. If you've hit a stumbling block, hop on the [Hyperapp Slack](https://hyperappjs.herokuapp.com) or drop by [Spectrum](https://spectrum.chat/hyperapp) to get support, and if you don't receive an answer, or if you remain stuck, please file an issue, and we'll try to help you out.
## Fundamentals
Is anything wrong, unclear, missing? Help us [improve this page](https://github.com/jorgebucaran/hyperapp/fork).
Hyperapp applications consist of a single state tree, a view that describes a user interface, and actions that describe state transitions. Every time your application state changes, Hyperapp calls the view function to create a new virtual representation of the DOM and uses it to update the actual DOM.
## Stay in the loop
<!-- Immutable state, unidirectional data-flow (state transitions via actions), effects as data and declarative event streams (subscriptions). -->
- [Twitter](https://twitter.com/hyperappjs)
- [Awesome](https://github.com/jorgebucaran/awesome-hyperapp)
- [/r/hyperapp](https://www.reddit.com/r/hyperapp)
It may seem wasteful to throw away the old virtual DOM and recalculate it entirely on every update—not to mention the fact that at any one time, Hyperapp is keeping two virtual DOM trees in memory, but as it turns out, browsers can create hundreds of thousands of objects very quickly. On the other hand, modifying the DOM is orders of magnitude more expensive.
In this section, we'll take a deep dive into the data lifecycle of a typical Hyperapp application as we build a to-do manager step-by-step. We'll look in great detail at how we initialize the state, render content on the page, and dispatch actions. Finally, we'll discuss how breaking down our view into functional components can improve code reusability and readability.
### Initializing the state
The state is a plain object that contains knowledge about your application at any given time; for example, a blog needs to know whether or not a user is logged in or how many posts they have published, a platform game might keep track of the character's coordinates on the screen, vertical velocity, direction, and so on.
Our to-do app will need an array to store to-do items and a string to watch what the user is currently typing into a text field. Each item will have an id and a value. We also want to know if the user is editing a particular item, and a way to undo changes if they cancel the operation.
```jsx
import { app } from "hyperapp"
app({
init: () => ({
value: "",
items: [
{
id: 1,
value: "Go outside",
isEditing: false,
lastValue: ""
}
]
})
})
```
The `init` action describes how to initialize the state. Think of it as the entry point of the program. Unlike [model–view–controller](https://en.wikipedia.org/wiki/Model–view–controller) and derivatives that encourage spreading the state out across different components, Hyperapp's state is consolidated in one place.
If we decide to start with more than one to-do items, we're going to run out of vertical space quickly. By creating new to-do items through a function, we can reduce code duplication and automate generating a unique id for each item using a base change algorithm. No warranty of any kind is implied, though. Use at your own risk.
> Using `Math.random` to generate random numbers is a side effect. We're taking a pragmatic approach to allow for side effects in this example out of convenience. You can read more about [generating random numbers] using Hyperapp effects later in the documentation.
```jsx
import { app } from "hyperapp"
const newItem = value => ({
id: Math.random().toString(36),
isEditing: false,
lastValue: "",
value
})
app({
init: () => ({
value: "",
items: [
newItem("Go outside"),
newItem("Wake up earlier"),
newItem("Learn a new language")
]
})
})
```
We have an initial state, but there's still no user interface to display it. What will our program will look like? In the next section, we'll learn how to render a page and look more closely at the virtual DOM model.
### Rendering a page
When describing the content of a page, we use the `h` function to create a virtual DOM. A virtual DOM is an object representation of how the DOM should look at any point in time. Hyperapp calls your `view` function to create this object and converts it into real DOM nodes in the browser.
A virtual DOM allows us to write code as if the entire document is thrown away and rebuilt on each transition, while we only update the parts that actually change. We do this in the least number of steps possible, by comparing the new virtual DOM against the previous one, leading to high-efficiency, since typically only a small percentage of nodes need to change, and changing real DOM nodes is costly compared to recalculating the virtual DOM.
<!-- prettier-ignore -->
```jsx
import { h, app } from "hyperapp"
app({
view: () =>
h("div", {}, [
h("article", {}, [
h("h2", {}, "What's Hyperapp?")
])
]),
node: document.getElementById("app")
})
```
We also need to tell Hyperapp where to render the view. Usually, you'll have a node with an `id="app"` or `id="root"` in your HTML for this purpose. You can use any type of node, even a text node. If the node isn't empty, Hyperapp will recycle its children instead of throwing away the existing content. This process is also called [hydration]. We'll discuss it later in the documentation.
Let's use what we've learned to render our to-do app with Hyperapp.
```jsx
import { h, app } from "hyperapp"
app({
view: () =>
h("div", {}, [
h("h1", {}, "To-Do"),
h("ul", {}, [
h("li", {}, "Go outside"),
h("li", {}, "Wake up earlier"),
h("li", {}, "Learn a new language")
]),
h("input", { type: "text", value: "" }),
h("button", {}, "New Item")
]),
node: document.getElementById("app")
})
```
If HTML tags in your JavaScript sound appealing, here's the same code using JSX. It requires a build step, but JSX tends to look like regular HTML, which can be a win for you or your team. We'll be using JSX for the rest of this document, but you can choose whatever works for you. Check out [`@hyperapp/html`](https://github.com/hyperapp/html) for an official alternative.
```jsx
import { h, app } from "hyperapp"
app({
view: () => (
<div>
<h1>To-Do</h1>
<ul>
<li>Go outside</li>
<li>Wake up earlier</li>
<li>Learn a new language</li>
</ul>
<input type="text" value="" />
<button>New Item</button>
</div>
),
node: document.getElementById("app")
})
```
Excellent! Now we have a user interface to work with. Next, we want to populate the list dynamically based on the current state. [Previously](#initializing-the-state), we learned how to initialize the application state, and we know the `view` function takes in the state, so let's put the two together.
```jsx
import { h, app } from "hyperapp"
const newItem = value => ({
id: Math.random().toString(36),
isEditing: false,
lastValue: "",
value
})
app({
init: () => ({
value: "",
items: [
newItem("Go outside"),
newItem("Wake up earlier"),
newItem("Learn a new language")
]
}),
view: state => (
<div>
<h1>To-Do</h1>
<ul>
{state.items.map(item => (
<li>{item.value}</li>
))}
</ul>
<input type="text" value={state.value} />
<button>Add</button>
</div>
),
node: document.getElementById("app")
})
```
The view is a way to view your state as HTML. The text field is synchronized with `state.value`, though, there's no way to update it yet, and by mapping through `state.items` we can turn the items array into an array of `<li>` nodes. There was no need to mutate the DOM manually, the markup is entirely declarative.
Eventually, you'll want to break down your view into reusable components. Hyperapp components are stateless functions that return virtual DOM nodes. Their input is the state or a part thereof; their output is the markup that represents the supplied state. Components make it easy to split your UI into chunks of content, styles, and behavior that belong together.
```jsx
const TodoList = props => (
<ul>
{props.items.map(item => (
<li>{item.value}</li>
))}
</ul>
)
```
Let's revisit our to-do app requirements. We want to add new, edit, and delete existing entries. When we're editing an item, it should also be possible to cancel the operation. As it turns out, we already have everything we need in the state. If you look at any to-do item, you'll find `isEditing`, a boolean flag set to `false`. We're going to use it to toggle a to-do's edit mode. While in edit mode, we'll show a text field, and buttons such as Cancel, Remove, and Save.
```jsx
const TodoList = props => (
<ul>
{props.items.map(item =>
item.isEditing ? (
<li>
<input type="text" value={item.value} />
<button>Cancel</button>
<button>Remove</button>
<button>Save</button>
</li>
) : (
<li>{item.value}</li>
)
)}
</ul>
)
```
To render the `TodoList` component, you can use it as you would any other element in the view. We've left out the state initialization code for brevity.
```jsx
app({
init: () => ({
// ...
}),
view: state => (
<div>
<h1>To-Do</h1>
<TodoList items={state.items} />
<input type="text" value={state.value} />
<button>Add</button>
</div>
),
node: document.getElementById("app")
})
```
Hyperapp components receive all the necessary state from their parent components, or in this case, the view function itself. Unlike the view function, however, components are not automatically wired to your application state.
We have a state and view to display it, but still no way to interact with the application. There's no way to toggle a to-do's edit mode; inputs and buttons aren't functional yet. In the next section, we're going to explore actions in detail. We'll learn how to respond to DOM events, and update the state, propagating changes back to the view.
### Dispatching actions
Actions describe state transitions: current state in, new state out. An action doesn't change the state in-place but yields a new state. We can dispatch actions in response to DOM events like clicks, mouse moves, key ups/downs, and so on, using the [on-event](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Event_handlers) attribute of the target element.
```jsx
<button onclick={Add}>New Item</button>
```
When the user clicks the button, the browser sends a click event to the button. Hyperapp intercepts the event to dispatch the specified action with the current state and event object as a payload and uses the return value of the action as the new state. At this point, the state and the DOM are out of sync. Next, it calls the view function to calculate a new virtual DOM and [schedules](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) the DOM to update before the next browser repaint, minimizing expensive layout reflows and further repaints.
Hyperapp's state is immutable. You initialize it, but you can't change it like you would any other object. Instead, changes are presented by creating a new object based on the current state; for example, here's the action to describe adding a new item in our to-do app.
```jsx
const Add = state => ({
...state,
value: "",
items: state.items.concat(newItem(state.value))
})
```
Notice how we use the [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals) to shallow-clone the state and merge it with the updated properties, creating a brand new object in the process. When adding a new to-do item, we join `state.items` and `state.value` into a new `items` array, and to clear the text field, set the current `value` to an empty string. Creating a new array to add, update or remove items is a common pattern when working with lists in an immutable fashion.
Immutability doesn't imply that the state is unwriteable. You can try to circumvent the state transition mechanism, but that's never a good idea. If you mutate a property in the state, Hyperapp doesn't know what has changed, potentially leading to a DOM out of sync with your state and unwanted side-effects. You've been warned.
Back to our to-do app. We've seen how to add new items, but how do we remove an item without mutating the original state? We know that every to-do item has an `id` property. The solution is to use the [filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) method on `state.items` and create a new array without the unwanted element.
```jsx
const Remove = (state, id) => ({
...state,
items: state.items.filter(item => item.id !== id)
})
```
What is of interest here is how the action receives the `id`. When we want Hyperapp to dispatch an action with a custom payload we use a 2-tuple action-value pair that consists of the action and any type of data we want to send as a payload.
```jsx
<button onclick={[Remove, item.id]}>Remove</button>
```
And for context, here's the new `TodoList` component. We also wired actions to the Save and Cancel buttons, and to each to-do list item to toggle the edit mode when clicked. You'll learn how they work after the code snippet.
```jsx
const TodoList = props =>
props.items.map(item =>
item.isEditing ? (
<li>
<input type="text" value={item.value} />
<button onclick={[Cancel, item.id]}>Cancel</button>
<button onclick={[Remove, item.id]}>Remove</button>
<button onclick={[ToggleEdit, item.id]}>Save</button>
</li>
) : (
<li onclick={[ToggleEdit, item.id]}>{item.value}</li>
)
)
```
Let's take a look at `ToggleEdit` first. Why is it used in two different elements? It's essentially a switch. The state needs to know if we're in edit mode or not; moreover, we're trying to make any item editable, that's why `isEditing` is in the to-do definition.
```jsx
const ToggleEdit = (state, id) => ({
...state,
items: state.items.map(item =>
item.id === id
? {
...item,
lastValue: item.value,
isEditing: !item.isEditing
}
: item
)
})
```
The gist of it is toggling `isEditing` on or off based on its current value, and saving `item.value` in `lastValue`. We're carrying a copy of each to-do's value at all times, which we'll use to reset `value` in `Cancel` shown below.
```jsx
const Cancel = (state, id) => ({
...state,
items: state.items.map(item =>
item.id === id
? {
...item,
value: item.lastValue,
isEditing: false
}
: item
)
})
```
In plain English, `Cancel` updates the item matching the supplied `id` in `items` by setting its `value` back to what it was before switching to edit mode, and sets `isEditing` to false to switch edit mode off.
If we look closely at either action, we'll notice a repetitive pattern: map through an array, find an element matching a given `id`, and update one or more properties in it. Wouldn't it be nice if we could refactor that into a function we can reuse later? Fortunately, we can.
```jsx
const setItem = (items, id, set) =>
items.map(item => (item.id === id ? { ...item, ...set(item) } : item))
const Cancel = (state, id) => ({
...state,
items: setItem(state.items, id, item => ({
value: item.lastValue,
isEditing: false
}))
})
const ToggleEdit = (state, id) => ({
...state,
items: setItem(state.items, id, item => ({
lastValue: item.value,
isEditing: !item.isEditing
}))
})
```
The result is less, and more readable code. A single glance at `Cancel` or `ToggleEdit` reveals what properties are changing in any given to-do, without getting bogged down in the how.
Look how far we've come. We can initialize an application with some state, visualize it, and dispatch actions to update it. Give or take, everything after this point is building upon the same ideas. In the next section, you'll learn how to handle text input, and access the event object and element that triggered an event. Hang on tight! We're almost done.
### Handling text input
So far we've seen how to dispatch an action when the user clicks on a button, but how do we update the state when the user types into a text field? Text fields are inherently stateful—how do we prevent the application state and DOM from getting out of sync? Let's find out.
To capture what the user is typing into a text field, we can assign an action to the oninput event attribute of the target element. And by setting the value attribute of the element to `state.value`, we guarantee that its internal state always mirrors what's in the state.
```jsx
<input type="text" value={state.value} oninput={NewValue} />
```
[Input events](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/input_event) fire not only on every keystroke but whenever the value of a text field changes; for example, by dragging text to or from the element, by cutting or pasting text either with or without the keyboard, or by using speech recognition to dictate the text.
Likewise, the onchange event occurs when the selection, the checked state or the value of an element have changed, however, for a text field, this event only fires when the element loses focus, whereas input events fire immediately on every change. [Change events](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event) are useful for validating forms as sometimes we don't want to display errors until the user is finished typing.
Now, how do we grab the changed value? The browser creates an [event object](https://developer.mozilla.org/en-US/docs/Web/API/Event) for every event, carrying detailed information about the event at the time it occurred: the type of the event, the event target, etc. Hyperapp sends this object as the payload to any action wired to a DOM event, allowing us to update `state.value` with the current value of the text field.
```jsx
const NewValue = (state, event) => ({ ...state, value: event.target.value })
```
When implementing an action, wouldn't it be better if we didn't have to think about events at all? Our current strategy may be sufficient if not involving a complex payload, but at what cost? Actions tightly coupled to events don't encourage code reusability; besides, destructuring the event object can become awkward. To tackle this problem, Hyperapp has payload creators.
First, let's rewrite `NewValue` to take in the new value as a payload. We'll figure out how to pass in the value in a moment.
```jsx
const NewValue = (state, value) => ({ ...state, value })
```
A payload creator is a function that allows you to transform the default payload into anything you want. You can use it to filter the event object to extract the value before it reaches the action. Here's a payload creator that grabs the event's target value.
```jsx
const targetValue = event => event.target.value
```
And here's how we use it when dispatching an action.
```jsx
<input type="text" value={state.value} oninput={[NewValue, targetValue]} />
```
Similarly, we need to handle text input for every to-do item while it's in edit mode, as well as send a custom payload with the action to identify which to-do item the user is editing. Sounds like a job for another payload creator.
```jsx
const Update = (state, { id, value }) => ({
...state,
items: setItem(state.items, id, () => ({ value }))
})
```
And here's our up-to-date `TodoList` component using it.
```jsx
const TodoList = props =>
props.items.map(item =>
item.isEditing ? (
<li>
<input
type="text"
value={item.value}
oninput={[Update, e => ({ id: item.id, value: targetValue(e) })]}
/>
<button onclick={[Cancel, item.id]}>Cancel</button>
<button onclick={[Remove, item.id]}>Remove</button>
<button onclick={[ToggleEdit, item.id]}>Save</button>
</li>
) : (
<li onclick={[ToggleEdit, item.id]}>{item.value}</li>
)
)
```
Neat! Whereas custom payloads allow us to send dynamic data into our actions without cluttering the state with intermediate values, payload creators give us full control of the data we can pass in, reducing coupling and improving code reuse. We have all the bits and pieces we need to put our to-do app together now. In the next section, we'll wrap things up and present the entire program in all its shining glory.
### Putting it all together
Here's the fruit of our work. Everything is in one place to help you see the big picture. In a real-world scenario, you'll want to split up your code into modules instead to reduce complexity and improve maintainability. Check out the final result [online] for a potential way to organize a Hyperapp project.
```jsx
import { h, app } from "hyperapp"
const targetValue = e => e.target.value
const setItem = (items, id, set) =>
items.map(item => (item.id === id ? { ...item, ...set(item) } : item))
const newItem = value => ({
id: Math.random().toString(36),
isEditing: false,
lastValue: "",
value
})
const NewValue = (state, value) => ({ ...state, value })
const Add = state => ({
...state,
value: "",
items: state.items.concat(newItem(state.value))
})
const Update = (state, { id, value }) => ({
...state,
items: setItem(state.items, id, () => ({ value }))
})
const Cancel = (state, id) => ({
...state,
items: setItem(state.items, id, item => ({
value: item.lastValue,
isEditing: false
}))
})
const Remove = (state, id) => ({
...state,
items: state.items.filter(item => item.id !== id)
})
const ToggleEdit = (state, id) => ({
...state,
items: setItem(state.items, id, item => ({
lastValue: item.value,
isEditing: !item.isEditing
}))
})
const TodoList = props =>
props.items.map(item =>
item.isEditing ? (
<li>
<input
type="text"
value={item.value}
oninput={[Update, e => ({ id: item.id, value: targetValue(e) })]}
/>
<button onclick={[Cancel, item.id]}>Cancel</button>
<button onclick={[Remove, item.id]}>Remove</button>
<button onclick={[ToggleEdit, item.id]}>Save</button>
</li>
) : (
<li onclick={[ToggleEdit, item.id]}>{item.value}</li>
)
)
app({
init: {
value: "",
items: [newItem("Make a sandwich")]
},
view: state => (
<div>
<h1>To-Do</h1>
<TodoList items={state.items} />
<input
type="text"
value={state.value}
oninput={[NewValue, targetValue]}
/>
<button onclick={Add}>New Item</button>
</div>
),
node: document.getElementById("app")
})
```
If you made it here, congratulations, we built a minimal, fully functional to-do app from scratch in just a few lines of code. We left out features like marking items as complete rather than deleting them, filtering, searching, and saving our data to local storage, but you should have a decent grasp of how Hyperapp works by now.
If you're up for the challenge, try implementing one or two new features; for example, it would be useful to cross-out a to-do to indicate you've completed a task without removing it from the list. Also nice to have is a button to clear the entire list in one go. Sometimes we need to start over to see things in a new light.
## Subscriptions
Sometimes we want to react to interesting events happening outside of our application like subscribing to location changes, or the current time. Did the user resize the browser's window? Maybe we're building a game and want to hook into the browser's natural repaint cycle. Subscriptions allow us to listen for such things.
The alternative, working with traditional event emitters, requires complicated resource management like adding and removing listeners, closing connections, clearing out intervals—not to mention testing asynchronous code is tricky. What happens when the source you are subscribed to shuts down? How do you cancel or restart a subscription?
Subscriptions describe a connection to an event generator. Similar to how we use a function to create virtual nodes instead of writing them out by hand, we use functions to create a subscription of the type of event we want to listen to. For scheduling recurrent tasks there is [`@hyperapp/time`](lib/time), for listening to global events like mouse or keyboard events there is [`@hyperapp/events`](). Need to use WebSockets for two-way communication? [`@hyperapp/websocket`]() has your back.
### Controlling time
Let's say you want to call a function every second or so. Maybe you're creating a turn-based game where each player has an allotted time to play. Usually, you'd use `setInterval` or concoct something with `setTimeout` to defer calling a function at a later date and keep track of the timeout ID so you can clear out the interval; for example, when you want to pause or resume the game. But what happens when the time needs to change dynamically? In some forms of chess, you have to add a certain number of seconds to the player's clock each time they move. Working with time will be our first foray into the world of subscriptions.
We're going to build a simple clock that shows the current time. You can try the [final result here]() before we dive into the code. When you're ready, install the [`@hyperapp/time`](lib/time) core package. Then, import the `interval` function into your program and create a new subscription as follows, specifying the action to dispatch and the interval delay. Unless otherwise stated, time is always measured in milliseconds.
```jsx
import { h, app } from "hyperapp"
import { interval } from "@hyperapp/time"
const Tick = (state, time) => ({ ...state, time })
app({
subscriptions: state => [
interval(Tick, {
delay: 1000
})
]
})
```
That is all there is to setting up a subscription. Hyperapp will dispatch `Tick` with the system timestamp as the payload every second. Behind the scenes, it uses the browser's [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) method to dispatch the action at the specifed time interval and [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) to retrieve the current time.
The `subscriptions` function takes in the current state and returns an array of subscriptions. Whenever the state changes, Hyperapp calls this function to calculate a new array. If a subscription appears in the array, we'll start it. If a subscription leaves the array, we'll cancel it. If any of its properties change, we'll restart it.
Here's the rest of the program. To spice things up, we've added an option to switch to a 24-hour clock too.
```jsx
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 })
app({
init: () => ({
time: Date.now(),
use24: false
}),
view: state => (
<div>
<h1>{posixToHumanTime(state.time, state.use24)}</h1>
<fieldset>
<legend>Settings</legend>
<label>
<input type="checkbox" checked={state.use24} oninput={ToggleFormat} />
Use 24 Hour Clock
</label>
</fieldset>
</div>
),
subscriptions: state => [
interval(Tick, {
delay: 1000
})
],
node: document.getElementById("app")
})
```
Now, let's say we need to hide the clock. Fair enough, but how do we clear out the interval? We don't want to trigger unnecessary renders and waste browser resources. By using a boolean condition we can switch a subscription on or off whenever the state changes and Hyperapp will take care of rewiring the connections for us under the hood. The main takeaway is: we can start or cancel a subscription without having to keep track of an ID or event listener, just like we can show or hide an element in the view without a reference to a DOM node.
```jsx
app({
subscriptions: state => [
state.isVisible &&
interval(Tick, {
delay: 1000
})
]
})
```
The [`@hyperapp/time`](lib/time) package is not a general date/time utility library. You won't find constants or functions to format, validate or manipulate time and time zones in it. It has one purpose. Make time effects and subscriptions available to Hyperapp programs. To learn more about this package, visit the official documentation with the link above.
### Listening to global events
Whenever you click or move the mouse anywhere on the screen, press or release a key, scroll the document view, or move across a touch surface, the browser will fire an event you can listen to. You can react to the browser's window or tab losing and gaining focus; for example, when the user looks away, you may want to cancel an expensive subscription, pause video or audio, and so on.
In this section you'll learn to respond to mouse and keyboard input, and sync up with the browsers natural refresh rate to create an interactive game. Even if your goal is not building games, taming the mouse and keyboard will be useful when registering application-wide keyboard shortcuts, implementing a drag and drop feature, and detecting when the user clicks outside of an element.
Let's start off with a couple of questions. What are the current mouse coordinates and what key was pressed? To begin, we'll need the [`@hyperapp/events`] core package, so make sure to install it first. Then, import the `onKey
```jsx
```
Typing practice
```jsx
```
Snake/blockade.
```jsx
```
### Implementing your own subscriptions
Think of subscriptions as orchestration for events or virtual DOM meets event streams. Or just a declarative API on top of the browser's imperative event-driven paradigm.
```jsx
const fx = a => b => [a, b]
export const Sub = fx((dispatch, props) => {
// Subscribe
return () => {
// Unsubscribe
}
})
```
## Effects
We run programs for their side effects. Likewise, we want our programs to be predictable, easy to compose, test, and parallelize. JavaScript's single-threaded execution guarantees that operations are atomic; two functions will never run at the same time, and we don't usually need to worry about concurrency, deadlock or race conditions (at least not the type of race condition caused by interleaved multi-threaded code), but we can still shoot ourselves in the foot by uncontrolled, indiscriminate use of side effects.
How can our programs be pure while conversing with the outside world? Rather than setting a timeout or making an HTTP request, an action can return a description of the work that needs to be done, and Hyperapp will figure out how to do the job behind the scenes. Like the view function returns a specification of the DOM, but doesn't touch the real DOM; an effect describes a side effect: creating an HTTP request, giving focus to an element, saving data to local storage, sending data over a WebSocket, etc., without executing any code.
{{TODO}}
{{
Effects are not built-into hyperapp, instead we need to import modules that produce the type of effects that we want. These modules encapsulate the implementation—the part that tells hyperapp exactly what to do. In this section, we'll walk through concrete examples that show how to use several Hyperapp effects to create timeouts talk to servers, generate random numbers, manipulate the DOM, and much more. Finally, we'll learn how to create custom effects and discuss when we might want to use them.
}}
### Delaying time
{{
Instead of having JavaScript execute a function immediately, you can tell it to execute a function after a certain period of time. We could've chosen from other more realistic examples, but the timeout's minimal api surface makes it a perfect candidate to introduce effects. Let's start with (example description) that demonstrates the gist of the idea. First, we'll import the effect we need from the `@hyperapp/time` module. Next, we'll create an action to (do that thing). Finally, we'll initialize the application and start the effect at the same time.
}}
```jsx
// TODO
```
{{
Up until this point we've used init to describe the initial state of our application, but like every other action, it can return a 2-tuple state-effect pair. Think of it as a sequence that describes what the state should be and the side effect we want to produce.
}}
### Talking to servers
{{TODO}}
```jsx
// Gif Search
```
```jsx
// Currency Converter
```
### Manipulating the DOM
```jsx
import { h, app } from "hyperapp"
import { focus } from "@hyperapp/dom"
const SetFocus = state => [state, focus({ id: "input" })]
app({
view: () => (
<div>
<input id="input" type="text" />
<button onclick={SetFocus}>Set Focus</button>
</div>
),
node: document.getElementById("app")
})
```
### Generating random numbers
```jsx
// Roll the dice game
```
### Implementing your own effects
```jsx
const fx = (action, dispatch) => dispatch(action)
const Invoke = action => [fx, { action }]
```
## HTML Attributes
{{TODO}}
### class
```jsx
import { h } from "hyperapp"
export const ToggleButton = ({ Toggle, isOn }) => (
<div class="btn" onclick={Toggle}>
<div
class={{
circle: true,
off: !isOn,
on: isOn
}}
/>
<span class={{ textOff: !isOn }}>{isOn ? "ON" : "OFF"}</span>
</div>
)
```
### style
```jsx
import { h } from "hyperapp"
export const Jumbotron = ({ text }) => (
<div
style={{
color: "white",
fontSize: "32px",
textAlign: center,
backgroundImage: `url(${imgUrl})`
}}
>
{text}
</div>
)
```
### checked
{{TODO}}
### selected
{{TODO}}
### multiple
{{TODO}}
### innerHTML
{{TODO}}
## Techniques
### Testing
{{TODO}}
### Hydration
{{TODO}}
- Hyperapp will try to hydrate child nodes instead of throwing away your server-side rendered content. Hydration recycles existing DOM (usually from server-side rendering) rather than create new elements.
- Hyperapp works transparently with SSR and pre-rendered HTML, enabling SEO optimization and improving your sites time-to-interactive. The process consists of serving a fully pre-rendered page together with your application.
- Then instead of throwing away the server-rendered markdown, we'll turn your DOM nodes into an interactive application.
- Hyperapp expects server side rendered content to be identical between the server and the client. You should treat mismatches as bugs and fix them.
### Navigation
{{TODO}}
```jsx
import { h, app } from "hyperapp"
import { location } from "@hyperapp/location"
app({
init: () => ({
location: location.state
}),
view: state => (
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
{state.location === "/"
? Home
: state.location === "/about"
? About
: state.location === "/topics"
? TopicsView
: NotFound}
}
</div>
),
subscriptions: state => [location.onLocationChange],
node: document.getElementById("app")
})
```
### Working with forms
{{TODO}}
```jsx
import { h, app } from "hyperapp"
import { preventDefault, stopPropagation } from "@hyperapp/events"
const SubmitForm = state => [
{ ...state, otherStuff },
preventDefault,
stopPropagation
]
app({
view: state => (
<form onsubmit={SubmitForm}>
<label>
Username:
<input type="text" value={state.username} oninput={UpdateUsername} />
</label>
<label>
Password:
<input type="text" value={state.password} oninput={UpdatePassword} />
</label>
</form>
),
node: document.getElementById("app")
})
```
### Using external libraries
{{TODO}}
Maybe you've run into a situation where Hyperapp alone isn't enough to do what you want and you are under a time constraint or need to step outside the boundaries set by a functional paradigm. In this section, we'll learn how to integrate a third-party library with a Hyperapp application.
### Animating elements
{{TODO}}
## Optimization
{{TODO}}
### Keys
```jsx
import { h, app } from "hyperapp"
```
### Lazy views
Immutability makes it cheap to figure out when things are the same. It guarantees that if two things are referentially equal (they occupy the same location in memory), they must be identical.
```jsx
import { h, Lazy, app } from "hyperapp"
```
Perf overhead you say? More like perf benefit!
## Examples
- [7GUI] - A GUI Programming Benchmark
- [Counter]
- [Temperature converter]
- [Flight booker]
- [Timer]
- [CRUD]
- [Circle drawer]
- [Spreadsheet]
- [TodoMVC] - Helping your select an MV\* framework
- [HNPWA] - Hacker News reader as a progressive web application
- [RealWorld] - Fullstack Medium-like clone
- [Starter Kit] - Everything you need to start building applications with Hyperapp
## Ecosystem
| Package | Version | About |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| [`hyperapp`](.) | [![npm](https://img.shields.io/npm/v/hyperapp.svg)](https://www.npmjs.com/package/hyperapp) | Hyperapp |
| [`@hyperapp/html`](/packages/html) | [![npm](https://img.shields.io/npm/v/@hyperapp/html.svg)](https://www.npmjs.com/package/@hyperapp/html) | Write HTML using functions in Hyperapp |
| [`@hyperapp/http`](/packages/http) | [![npm](https://img.shields.io/npm/v/@hyperapp/http.svg)](https://www.npmjs.com/package/@hyperapp/http) | Make HTTP requests in Hyperapp |
| [`@hyperapp/time`](/packages/time) | [![npm](https://img.shields.io/npm/v/@hyperapp/time.svg)](https://www.npmjs.com/package/@hyperapp/time) | Time effects and subscriptions for Hyperapp |
## License
[MIT](LICENSE.md)

@@ -126,10 +126,11 @@ var RECYCLED_NODE = 1

var createNode = function(vnode, listener, isSvg) {
var createNode = function(vdom, listener, isSvg) {
var ns = "http://www.w3.org/2000/svg"
var props = vdom.props
var node =
vnode.type === TEXT_NODE
? document.createTextNode(vnode.name)
: (isSvg = isSvg || vnode.name === "svg")
? document.createElementNS("http://www.w3.org/2000/svg", vnode.name)
: document.createElement(vnode.name)
var props = vnode.props
vdom.type === TEXT_NODE
? document.createTextNode(vdom.name)
: (isSvg = isSvg || vdom.name === "svg")
? document.createElementNS(ns, vdom.name, { is: props.is })
: document.createElement(vdom.name, { is: props.is })

@@ -140,6 +141,6 @@ for (var k in props) {

for (var i = 0, len = vnode.children.length; i < len; i++) {
for (var i = 0, len = vdom.children.length; i < len; i++) {
node.appendChild(
createNode(
(vnode.children[i] = getVNode(vnode.children[i])),
(vdom.children[i] = getVNode(vdom.children[i])),
listener,

@@ -151,7 +152,7 @@ isSvg

return (vnode.node = node)
return (vdom.node = node)
}
var getKey = function(vnode) {
return vnode == null ? null : vnode.key
var getKey = function(vdom) {
return vdom == null ? null : vdom.key
}

@@ -379,3 +380,3 @@

var createTextVNode = function(value, node) {
return createVNode(value, EMPTY_OBJ, EMPTY_ARR, node, null, TEXT_NODE)
return createVNode(value, EMPTY_OBJ, EMPTY_ARR, node, undefined, TEXT_NODE)
}

@@ -391,3 +392,3 @@

node,
null,
undefined,
RECYCLED_NODE

@@ -405,3 +406,3 @@ )

export var h = function(name, props) {
for (var vnode, rest = [], children = [], i = arguments.length; i-- > 2; ) {
for (var vdom, rest = [], children = [], i = arguments.length; i-- > 2; ) {
rest.push(arguments[i])

@@ -411,9 +412,9 @@ }

while (rest.length > 0) {
if (isArray((vnode = rest.pop()))) {
for (var i = vnode.length; i-- > 0; ) {
rest.push(vnode[i])
if (isArray((vdom = rest.pop()))) {
for (var i = vdom.length; i-- > 0; ) {
rest.push(vdom[i])
}
} else if (vnode === false || vnode === true || vnode == null) {
} else if (vdom === false || vdom === true || vdom == null) {
} else {
children.push(typeof vnode === "object" ? vnode : createTextVNode(vnode))
children.push(typeof vdom === "object" ? vdom : createTextVNode(vdom))
}

@@ -426,6 +427,6 @@ }

? name(props, children)
: createVNode(name, props, children, null, props.key)
: createVNode(name, props, children, undefined, props.key)
}
export var app = function(props, enhance) {
export var app = function(props) {
var state = {}

@@ -454,8 +455,8 @@ var lock = false

var dispatch = (enhance ||
function(any) {
return any
})(function(action, props, obj) {
var dispatch = (props.middleware ||
function(obj) {
return obj
})(function(action, props) {
return typeof action === "function"
? dispatch(action(state, props), obj || props)
? dispatch(action(state, props))
: isArray(action)

@@ -465,7 +466,6 @@ ? typeof action[0] === "function"

action[0],
typeof (action = action[1]) === "function" ? action(props) : action,
props
typeof action[1] === "function" ? action[1](props) : action[1]
)
: (batch(action.slice(1)).map(function(fx) {
fx && fx[0](dispatch, fx[1], props)
fx && fx[0](dispatch, fx[1])
}, setState(action[0])),

@@ -472,0 +472,0 @@ state)

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc