Component
A simple JavaScript component thing
[![npm version](https://badge.fury.io/js/%40scottjarvis%2Fcomponent.svg)](https://badge.fury.io/js/%40scottjarvis%2Fcomponent) [![Node version](https://badgen.net/npm/node/@scottjarvis/component)](http://nodejs.org/download/) [![Build Status](https://travis-ci.org/sc0ttj/component.svg?branch=master)](https://travis-ci.org/sc0ttj/component) [![bundle size](https://badgen.net/bundlephobia/minzip/@scottjarvis/component?color=green&label=gzipped)](https://badgen.net/bundlephobia/minzip/@scottjarvis/component) [![Downloads](https://badgen.net/npm/dt/@scottjarvis/component)](https://badgen.net/npm/dt/@scottjarvis/component)
**Component** is a simple, "stateful component" thing.
It lets you create re-usable, "functional components" - basically functions that have a "state".
A "state" is a snapshot of your application data at a specific time.
## Features
- Easy setup, **zero dependencies**:
- no compilation or build tools needed
- Simple syntax, [quick to learn](#quickstart), **easy to use**:
- plain, vanilla JavaScript only!
- plain HTML & CSS, no virtual DOM or JSX
- should work with any test suite
- Very **lightweight & modular** - use only what you need, for example:
- *~2.5 kb*, using the main `Component` library (_lots_ of features)
- *~810 bytes*, using only the standalone `htmel` module (for JSX-like HTML templating)
- *~830 bytes*, using only the standalone `render` module (for "debounced" DOM diffing at 60fps)
- Works **client-side**, in browsers:
- auto re-render on state change
- good (re)rendering/animation performance at 60fps, using `requestAnimationFrame`
- DOM diffing uses real DOM Nodes (not VDOM)
- or use an HTML5 `
` instead of a DOM-based view
- Works **[server-side](#server-side-rendering)**, in Node:
- render your components as strings (HTML, or stringified JSON)
- render your components as data (JS objects, Arrays, etc)
- Easy **state management**:
- immutable states, can only be updated through `setState()` (_optional_)
- define ["actions"](#using-actions) to easily update the state in specific ways
- keep all states in a [state history](#using-the-state-history), for debugging (_optional_):
- rewind or fast-forward to any point in the state history
- save/load current or any previous state as "snapshots"
- Easy CSS **[component styling](#using-component-css)**:
- Automatic "scoping"/prefixing of your component CSS (_optional_)
- Re-render styles on component CSS change (_optional_)
- Supports **"[middleware](#using-middleware)"** functions:
- easily customise a components setState and re-render behaviour
- Supports **nested components**:
- embed components in the "views" of other components
- supports various methods and syntaxes
- Works with these **optional add-ons**:
- [`validator`](#using-state-validation): validate states against a schema (_like a simple PropTypes_)
- [`html`/`htmel`](#using-html-and-htmel-modules): simpler, more powerful Template Literals (_like a simple JSX_)
- [`ctx`](#using-the-ctx-module): an enhanced 2d <canvas> with extra shapes, methods, and a chainable API
- [`emitter`](#using-the-emitter-module): an event emitter - share updates between components
- [`storage`](#using-the-storage-module): enables persistent states (between page refreshes, etc)
- [`syncTabs`](#using-the-synctabs-module): Synchronize state updates & page renders between browser tabs
- [`tweenState`](#using-the-tweenstate-module): animate nicely from one state to the next (tweened)
- [`springTo`](#using-the-springto-module): animate nicely from one state to the next (using spring physics)
- [`onScroll`](#using-the-onscroll-module): enables easy scroll-based animations (using debounced scroll event)
- [`useAudio`](#using-the-useaudio-module): add dynamic audio to your components (uses Web Audio API)
- [`onLoop`](#using-the-onloop-module): a fixed-interval loop, suitable for games, animations, time-dependant stuff
- [`geo`](#using-the-geo-module): an easy way to create Mercator or Robinson projected world maps
- [`chart`](#using-the-chart-module): an easy way to create charts, graphs and plotting things
- [`react-hooks`](#using-the-react-hooks-module): an alternative, more React-like API add-on
- [`devtools`](#using-the-devtools-module): enables easier component debugging in the browser
---
## Quickstart
Here's some quick examples to demo how it all looks, generally:
### "Counter" app
```js
const Counter = new Component({ count: 1 });
const add = num => Counter({ count: Counter.state.count + num })
Counter.view = props => htmel
`
Counter: ${props.count}
+
-
`;
Counter.render('.container')
```
### "Todo list" app
```js
const Todo = new Component({ txt: '', list: [ "one" ] });
const setText = e => Todo({ txt: e.target.value });
const addItem = e => Todo({ list: [ ...Todo.state.list, Todo.state.txt ] });
Todo.view = props => htmel
``;
Todo.render('.container')
```
### A *re-usable* HTML component:
Here is a "factory" function that generates _re-usable_ components - a new component is created and returned each time it's called.
```js
function Header(state) {
const Header = new Component({ title: "Hello world", ...state });
Header.view = props => `${props.title} `;
return Header;
}
// And you use it like this:
const header1 = new Header();
// Add it to our page
header1.render('.container');
// Update the state, the heading will re-render for you
header1.setState({ title: "Hello again!" });
// Or set state via the component constructor
header1({ title: "Hello a 3rd time!" });
```
### Using the ``
Your component "views" don't have to be HTML or SVG, they can use a `` element, instead:
```html
```
Also see the [`Ctx` add-on](#using-the-ctx-module) and the [`onLoop` add-on](#using-the-onloop-module) add-on for more `` related info.
### Nested components
Child components should be regular functions that return part of the view of the parent component:
```js
const Foo = new Component({ title: "Hey!", items: [ "one", "two" ] });
const Header = txt => `${txt} `
const List = i => `${i.map(item => `${i} `).join('')} `
Foo.view = props =>
`
${Header(props.title)}
${List(props.items)}
`
Foo.render('.container');
```
But you can also nest proper (stateful) components inside other components, too:
```js
// create a re-usable button component
function Button(state) {
const Button = new Component(state);
Button.view = props => htmel`${props.txt} `;
return Button;
}
// create 3 buttons from the re-usable component
const btn1 = new Button({ txt: "1", fn: e => alert("btn1") });
const btn2 = new Button({ txt: "2", fn: e => alert("btn2") });
const btn3 = new Button({ txt: "3", fn: e => alert("btn3") });
// create the parent component
const Menu = new Component({ txt: 'Click the buttons!' });
// put the styles in the parent component
Menu.style = props => `
button { border: 2px solid #999; }
`
// create a view with our buttons included
Menu.view = props => htmel`
${props.txt}
${btn1}
${btn2}
${btn3}
`;
Menu.render('.container');
```
See more short recipes in [examples/recipes.js](examples/recipes.js).
---
## Installation
### In browsers:
```html
```
### In NodeJS:
```
npm i @scottjarvis/component
```
Then add it to your project:
#### ES6 Modules:
```js
import { Component } from '@scottjarvis/component'
// use it here
```
#### Using NodeJS `require()`:
```js
var { Component } = require('@scottjarvis/component');
// use it here
```
See each add-on module (`validator`, `html`, `htmel`, `emitter`, `storage`, `tweenState`, etc) for their respective installation instructions.
---
## Usage
### In browsers
Create interactive components for your web pages:
- [examples/usage-in-browser--table.html](examples/usage-in-browser--table.html)
- [examples/usage-in-browser.html](examples/usage-in-browser.html)
### In NodeJS
Or you can do "server-side rendering" (SSR) of your components:
See [examples/usage-in-node.js](examples/usage-in-node.js)
Or run it in your terminal:
```
node examples/usage-in-node.js
```
## Component API overview
These are the methods and properties attached to the components you create.
### Methods:
- **.setState(obj)**: update the component state, triggers a re-render
- **.render(el)**: (re)render to the given element on state change (browser)
- **.toString()**: render your component as a string on state change (for NodeJS)
- **.view(props)**: receives a state and sets the component view to (re)render (_optional_)
- **.style(props)**: receives a state and sets the `
Total so far = 101
Click here
"
```
^ Any styles are wrapped in a `