Skip to content

Commit

Permalink
doc: update README
Browse files Browse the repository at this point in the history
  • Loading branch information
pemrouz committed Jul 3, 2016
1 parent 399e992 commit 1bf8fe7
Showing 1 changed file with 285 additions and 15 deletions.
300 changes: 285 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,298 @@
# [Ripple Minimal](https://github.com/pemrouz/ripple)
# [Minimal](https://github.com/rijs/minimal)

This is a minimal build of Ripple (~3 kB), with only a few of the [client modules](https://github.com/rijs/minimal/blob/master/src/index.js). This kind of build is useful if you are only using Ripple on the client without using any of the server modules, or want a lightweight solution to use [Vanilla Web Components](https://github.com/pemrouz/vanilla) alongside your existing application with a different architecture.
Tiny (~4kB), super-fast, refined, reactive, fractal, unidirectional, isomorphic, pro-standards, declarative, Just Works™ framework for frontend development. There's only one API:

**Quick Start:** Put the following in your Chrome address bar to see an example:
```js
import ripple from 'rijs.minimal'

ripple(name) // getter
ripple(name, body) // setter
ripple.on('change', (name, change) => {})
```

Get and set things in single store. A `change` event is emitted when something is updated, enabling reactive updates.

<br>
## Components

Define a component:

```js
ripple('my-component', () => {})
```

Use it on the page:

```html
<my-component>
```

Ripple is agnostic to _how_ you write your components, they should just be idempotent (a single render function). This is fine:

```js
ripple('my-app', (d, i, el) => el.innerHTML = 'Hello World')
```

Or using some DOM-diff helper:

```js
ripple('my-app', (d, i, el) => diff(el)`<h1>Hello World</h1>`)
```

Or using [once](https://github.com/utilise/once#once)/D3 joins:

```js
ripple('my-app', (d, i, el) => {
once(el)
('h1', 1)
.text('Hello World')
})
```

For more info about writing idempotent components, see [this spec](https://github.com/pemrouz/vanilla).

<br>
## State/Data

The first parameter of the component contains all the state and data the component needs to render:

```js
export default function component(data){ ... }
```

You can pass down data by adding the name of the resources to the data attribute:

```html
<my-shop data="stock">
```

```js
export default function shop({ stock }){ ... }
```

Declaring the data needed on a component is used to reactively rerender it when the data changes.

The other option is to explicitly pass down data to the component using the (D3) data binding:

```js
once(node)
('my-shop', { stock })
```

If you want to just use DOM, you can invoke `.draw()` on a custom element to redraw it:

```js
const shop = document.createElement('my-shop')
document.body.appendChild(shop)
shop.state = { stock }
shop.draw()
```

<br>
## Defaults

You can set defaults using the ES6 syntax:

```js
export default function shop({ stock = [] }){ ... }
```

If you need to persist defaults on the component's state object, you can use a small [helper function](https://github.com/utilise/utilise#--defaults):

```js
export default function shop(state){
const stock = defaults(state, 'stock', [])
}
```

<br>
## Updates

#### Local state

Whenever you need to update local state, just change the `state` and invoke a redraw (like a game loop):

```js
export default function shop(state, i, el){
const o = once(el)
, { counter = 0 } = state

o('span', 1).text(counter)
o('button', 1)
.text('increment')
.on('click' d => {
state.counter++
o.draw()
})
}
```

#### Global state

Whenever you need to update global state, you can simply compute the new value and register it again which will trigger an update:

```js
ripple('stock', {
apples: 10
, oranges: 20
, pomegranates: 30
})
```
data:text/html, <head><script src="https://rawgit.com/rijs/minimal/master/dist/ripple.js"></script><script>ripple('x-foo', function(){ this.innerHTML = 'foo' })</script><body><x-foo>

Or if you just want to change a part of the resource, use a [functional operator](https://github.com/utilise/utilise#--set) to apply a finer-grained diff and trigger an update:

```js
update('pomegranates', 20)(ripple('stock'))
// same as: set({ type: 'update', key: 'pomegranate', value: 20 })(ripple('stock'))
```

Using logs of atomic diffs combines the benefits of immutability with a saner way to synchronise state across a distributed environment.

You can also use the list of all relevant changes since the last render in your component via `element.changes` to make it more performant.

<br>
## Events

Dispatch an event on the root element to communicate changes to parents (`node.dispatchEvent`).

<br>
## Routing

Just invoke a redraw of your application when the route has changed:

```js
export function app(d, i, el){
const o = once(el)

o('h1', 1)
.text('You are currently on: ' + location.pathname)

window.on('change', d => el.draw())
}
```

[Decouter](https://github.com/pemrouz/decouter) emitterifies `window` to give you the `change` event, `go(url)` for navigating, and sets `location.params` with current route parameters.

<br>
## Bundling

Ripple does not care how you load/bundle your resources. You only just need to register them at some point. This means you are free to use whatever tool chain you like:

```js
// index.js
ripple('my-app', require('./resources/my-app'))
ripple('my-app.css', file('./resources/my-app.css'))
ripple('somedata', require('./resources/data/some'))
```

```
$ browserify index.js > app.js
```

```html
<html>
<head>
<script src="https://rawgit.com/rijs/minimal/master/dist/ripple.js"></script>
<script>ripple('x-foo', function(){ this.innerHTML = 'foo' })</script>
</head>
<body>
<x-foo></x-foo>
</body>
</html>
<script src="app.js"></script>
<my-app></my-app>
```

An application is just a component that composes other components, so you shouldn't need any other scripts.

<br>
## Folder Convention

I recommend using the folder convention: a `resources` directory, with a folder for each component, and a `data` folder for data resources.

```
resources
├── data
│ ├── stock.js
│ └── order.js
└── my-app
│ ├── my-app.js
│ ├── my-app.css
│ └── test.js
└── another-component
├── another-component.js
├── another-component.css
└── test.js
```

You can then use a [helper script](https://github.com/rijs/export#ripple--export) to automatically generate a single `require`able `index.js` from a directory of resources.

<br>
## Debugging

* Check `ripple.resources` for a snapshot of your application. Resources are in the [tuple format](https://github.com/rijs/core#ripple--core) `{ name, body, headers }`.

* Check `$0.state` on an element to see the state object it was last rendered with or manipulate it.

<br>
## Middleware

By default the draw function just invokes the function on an element. You can extend this without any framework hooks using the explicit decorator pattern:

```js
// in component
export default function component(d, i, el){
middleware(d, i, el)
}

// around component
export default middleware(function component(d, i, el){

})

// for all components
ripple.draw = middleware(ripple.draw)
```

**Bonus:** Register a new version of the component `x-foo` in the console and see all instances on the page change:
A couple of useful middleware included in this build are:

### Needs

[This middleware](https://github.com/rijs/needs#ripple--needs) reads the `needs` header and applies the attributes onto the element. The component does not render until all dependencies are available. This is useful when a component needs to define its own dependencies.

```js
ripple('x-foo', function(){ this.innerHTML = 'bar' })
export default {
name: 'my-component'
, body: function(){}
, headers: { needs: '[css=..][data=..]' }
}
```

### Helpers

[This middleware](https://github.com/rijs/helpers#ripple--helpers) makes the specified helper functions available from the resource (hidden properties). This is useful to co-locate all logic for each resource in one place.

```js
export default {
name: 'stock'
, body: {}
, headers: { helpers: { addNewStock, removeStock }}
}
```

### Styling

Stylesheet(s) can be modularly applied to an element: [This middlware](https://github.com/rijs/precss#ripple--precss) simply reads the `css` attribute and inserts them in the shadow root or scopes them and adds to `head`:

```js
ripple('some.css', `:host { background: red }`)
```
```html
<head>
<style>my-shop { background: red }</style>
</head>
<my-shop css="some.css">
```

<br>
## Fullstack

If you have a backed for your frontend, checkout [rijs/fullstack](https://github.com/rijs/fullstack) which transparently adds a few more modules to synchronise state between client-server or for more docs.

You can also adjust your own framework by [adding/removing modules](https://github.com/rijs/minimal/blob/master/src/index.js#L1-L11).

<br>
## Flavours

`dist/ripple.js` provides `ripple` and also [some small, useful, high power-to-weight ratio functions that enriches the language grammar](https://github.com/utilise/utilise#lean-javascript-utilities-as-micro-libraries). If you don't want the helper functions, use `dist/ripple.pure.js`. Add `.min` for prod. Minified and gzipped the sizes are ~12kB and ~4kB respectively.

0 comments on commit 1bf8fe7

Please sign in to comment.