A reactive frontend framework for JavaScript
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
dist
samples
src
test
.babelrc
.editorconfig
.eslintrc
.gitignore
.travis.yml
CHANGELOG.md
LICENSE
README.md
cyclow.svg
gulpfile.babel.js
package.json
wdio.conf.js

README.md

logo

npm version Build Status Join the chat at https://gitter.im/cyclow/Lobby

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 addition, 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 example:

  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
    }
  }

You can track state changes too:

  on: {
    state: state => console.log('State', state)
  }

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 is div)
  • attrs: Attributes (like id, class or style).
  • on: Events handlers (like click). 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}) }
  }

If you prefer, you can use h hyperscript helper:

  import {Block, run, h} from 'cyclow'

  const Counter = () => Block({
    on: {
      'in.init':  () => state => 0,
      'dom.click': () => state => state + 1
    },
    view: state => view: state => h('button', {on: {click: 'click'}}, `Count: ${state}`)
  })

  run(Counter)

Renderer

A renderer is just a component factory. It creates a component that takes a Virtual DOM Element as an 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

  • Virtual DOM diff and patching
  • A way to focus a DOM element
  • A TodoMVC sample
  • A cool logo
  • JavaScript Standard Style
  • A contributing guide
  • More e2e tests
  • More documentation
  • More samples