cyclow
v0.5.3
Published
A reactive frontend framework for JavaScript
Downloads
4
Readme
cyclow is a reactive frontend framework for JavaScript. It's inspired by another frameworks like Cycle.js and TSERS. It uses graflow as stream library.
Example
This a simple counter example:
import { Block, run } from 'cyclow'
const Counter = () => Block({
on: {
'in.init': () => state => 0,
'dom.click': () => state => state + 1
},
view: state => ({
tag: 'button',
on: {click: 'click'},
content: `Count: ${state}`
})
})
run(Counter)
Try it online!
More samples
You can build and open samples in a browser:
git clone https://github.com/pmros/cyclow
cd cyclow
npm install
npm run samples
Samples include a TodoMVC sample.
You can find even more samples at JS Comp and compare them with another implementations using frameworks like React, Angular or Cycle.
Why cyclow?
There are many JavaScript frameworks so... why another one? Well I really like Cycle.js. It's a nice reactive framework. TSERS is like Cycle.js and it adds a simple state manager and another features. But both are too pure (in the functional programmming sense) for me.
With cyclow instead of thinking in a big global model and pure functions, you have to think in components with inputs, outputs and their own state (something like an electronic circuit). I think cyclow is more intuitive and easier while it's still reactive and quite declarative. You can compare cyclow and Cycle.js samples at JS Comp.
cyclow goal is to help you to create applications that are:
- Declarative
- Easy to code
- Easy to read
- Scalable
How it works
A cyclow app consits of a block. A block is composed of other blocks. A block is graflow component, it receives messages and send messages async.
Every block contains this default blocks:
in
events
state
view
dom
out
In adition, you can add your own blocks or custom blocks with blocks
Block option.
Every block inside a block is connected through a bus
block, sending and receiving messages. Bus connect blocks forming a cycle.
Messages has three parts:
- Block
- Signal
- Value
You can handle messages with on
Block option.
Finally, you can transform state into a Virtual DOM Element with view
Block option. Virtual DOM Element will be converted into a real DOM by the renderer.
How To
How to set the initial state?
At the beginning, every block receives an init
signal from in
block. So you can handle this message to set a initial state.
From the counter example:
on: {
'in.init': () => state => 0
}
In this case, the handler takes no params ()
and returns a state transformation state => 0
. It's a function that takes the current state and returns the next state.
How to handle DOM events?
First, you have to catch the DOM event in view
Block option. From counter exaple:
view: state => ({
tag: 'button',
on: {click: 'click'},
content: `Count: ${state}`
})
Then, you can handle DOM event as a normal block message (from dom
block):
on: {
'dom.click': () => state => state + 1
}
If you need DOM event information, see Inputbox sample:
on: {
...
'dom.text': newText => text => newText,
},
view: text => ({content: [
{tag: 'input',
attrs: {id: 'myInput', value: text},
on: {keyup: (e, next) => next({text: e.target.value})}
}
...
]})
How to compose blocks?
See Composition sample:
blocks: {field: Field()},
on: {
'in.init': () => [{'field.init': 'Enter name'}, state => ({current: 'Steve'})],
'field.submission': submission => state => ({current: submission})
}
How to focus a DOM element?
See Inputbox sample:
on: {
'dom.focus': () => ({'dom.action': node => node.firstElementChild.focus()})
}
How to use LocalStorage to save the state?
See TodoMVC sample:
on: {
'in.init': () => state => JSON.parse(localStorage.getItem('todomvc')) || initial(),
state: state => { localStorage.setItem('todomvc', JSON.stringify(state)) }
}
How to debug a cyclow app?
You can log every message through bus
block:
on: {
bus: msg => {
console.log('Message', msg)
return msg
}
}
Virtual DOM Element
cyclow represents DOM elements as Virtual DOM Elements, that is a simple Javascript object with the following (optional) properties:
tag
: HTML tag (default isdiv
)attrs
: Attributes (likeid
,class
orstyle
).on
: Events handlers (likeclick
). It can be just an event message or a function that receive the DOM event and a function to send an event message.content
: Content can be just text, a Virtual DOM Element or an array of Virtual DOM Elements.
This is a virtual DOM element example:
{
tag: 'input',
attrs: { id: 'myInput' },
on: { keyup: (e, next) => next({text: e.target.value}) }
}
Renderer
A renderer is just a component factory. It creates a component that takes a Virtual DOM Element as a input and it converts into a Real DOM Element and it updates the HTML document. cyclow uses snabbdom as default renderer.
A renderer is a function that it takes target
, that the DOM element id where you want to insert into the Virtual DOM Element. If you don't specify target
, cyclow will append the app at the end of body.
API
run(MainComponent, options={})
Arguments:
MainComponent
: A component factory.options
:target
(document.body
by default)renderer
(SnabbdomRenderer
by default)init
({}
by default)
Block(options)
Arguments:
options
:blocks
on
view
Returns: A graflow component
TODO
- [x] Virtual DOM diff and patching
- [x] A way to focus a DOM element
- [x] A TodoMVC sample
- [x] A cool logo
- [ ] JavaScript Standard Style
- [ ] More documentation
- [ ] More samples
- [ ] Functional tests