Skip to content

Latest commit

 

History

History
293 lines (224 loc) · 8.17 KB

usage.md

File metadata and controls

293 lines (224 loc) · 8.17 KB

Getting Started

Intro

stardux builds upon the container concept introduced in redux. Containers provide a predictable interface around DOM elements. State is represented in objects and expressed in the DOM with ES6 template notation.

Containers use reducers and middleware to transform input into state. If you are familiar with redux and reducers then this should be easy for you. DOM updates are made possible with starplate and it's internal patching algorithm built on IncrementalDOM.

Containers

Containers are created with the [createContainer(domElement[,initialState = null, ...reducers])] (api.md#createcontainer) function.

import { createContainer } from 'stardux'
const domElement = document.querySelector('#dom-element')
const container = createContainer(domElement)

Once created, containers provide mechanisms for composition, manipulation, state pipes, and more.

Templating

Containers provide a way to express state to the DOM. A containers DOM can be expressed with ES6 template notation and thefore make it easy to create resuable dynamic views.

import { createContainer } from 'stardux'
const hello = document.createElement('div')
const container = createContainer(hello)

// Define the innerHTML as a template
hello.innerHTML = 'Hello ${ name }'
container.update({ name: 'kinkajou' })

// realize DOM change
console.log(hello.innerHTML) // "Hello kinkajou"

Functions can even be used in templates. However, functions will lose scope when they are referenced in template strings as they are executed in a sandboxed environment. Consider the following example of a function named now() that simple returns a string representation of a Date object. We'll define the function for the container at initialization. You can define the initial state of a container by passing an object to the second argument of the createContainer() function.

const date = document.createElement('div')
const container = createContainer(date, { now: _ => Date() })
date.innerHTML = '${ now() }'
container.update()
console.log(date.innerHTML) // "Tue Nov 24 2015 08:41:00 GMT-0500 (EST)"

Pipes

Container pipes, much like node streams provide a mechanism for data propagation. Containers consume updates, transform data with middleware, and reduce into state. This processes is repeated for each container in a pipeline.

Here 3 containers are constructed into a pipeline A -> B -> C.

containerA.pipe(containerB).pipe(containerC)

When an update occurs on containerA, it is propagated to containerB and then to containerC. This is effectively the samething as composition.

Middleware

Middleware provides a way of tapping into the root reducer of a container and allows consumers to transform the current state and action objects in the reducer chain.

They are installed with the .use(fn) method.

container.use((state, action) => {
  // middleware here ...
})

Reducers

Reducers are constructed and passed to the createContainer() function or to the Container constructor. Reducers are passed directly to the internal redux store.

Middleware is always invoked before reducers.

const container = new Container(domElement, (state, action) => {
  // reducer here ...
})

Composition

Containers can be composed together for update propagation and data filtering. Container composition allows for powerful, flexible, and reusable container pipelines.

import { createContainer, compose } from 'stardux'
const input = document.createElement('input')
const output = document.createElement('div')
const container = createContainer(input)

// DOM template
output.innerHTML = '${ value }'

// compose containers
compose(container, output)

// update container when user input changes
input.onchange = _ => container.update({ value: input.value })

// render to DOM
document.appendChild(input)

When user input changes the innerHTML of the output DOM element is updated as well.

The compose() function accepts n DOM elements or Container instances and composes an update pipe line in the order they were given.

NOTE- A .decompose() method is attched to the returned composite container object. Once called it is deleted with delete composed.decompose.

Manipulation

Container manipulation is made possible by a similar API to that of the DOM. Methods like appendChild(), removeChild(), and contains() are available on call containers. They accept instances of a Container or a DOM element. When an insert or removal occurs the container and its children are realigned. DOM patches also occur to align DOM tree with container tree.

const container = new Container()
const childA = new Container()
const childB = new Container()
const childAa = new Container()
const childAb = new Container()
const childBa = new Container()
const childBb = new Container()

container.appendChild(childA)
container.appendChild(childB)

childA.appendChild(childAa)
childA.appendChild(childAb)

childB.appendChild(childBa)
childB.appendChild(childBb)

We can check if a container is a descendant of another container with the contains() method.

container.contains(childBb) // true

Passing false as a second argument forces the method to check only direct descendants of the container.

container.contains(childBb, false) // false

Containers can be removed with the removeChild() method. Child DOM nodes are also removed.

container.removeChild(childB)
container.contains(childB) // false

Nesting

Containers can be nested and still act independent of each other. Containers can propagate updates to their child containers. This makes updating multiple containers who share similar data easy.

The following DOM structure yields a container with two child containers.

<div class="parent">
  <div class="a">${ value }</div>
  <div class="b">${ value }</div>
</div>

The containers can be implemented with the following Javascript. childA and childB are implicit children of parent and therefore do not need to explicitly added with appendChild().

const parent = createContainer(document.querySelector('.parent'))
const childA = createContainer(document.querySelector('.a'))
const childB = createContainer(document.querySelector('.b'))

Both childA and childB share the variable value. Updates to parent will propagate to childA and childB.

parent.update({value: 'hello'})

This makes updates to many containers easy. However, childA and childB are still independent containers. Updates to childA are made possible by a simple call to childA.update({value: 'world'}). childB remains unchanged while childA has.

JSON

Containers can be serialized into a JSON string and restored later. This make it possible to create container structures on the server and restored on the client where knowing the IDs of containers ahead of time is important.

Consider the following JSON structure.

{
  "id": ".9855624e",
  "src": "${ valueA }\n",
  "children":[
    {
      "id":".de0278b8",
      "src":" ${ valueB } ",
      "children":[]
    }
  ]
}

When restored with the restoreContainerFromJSON() function a container and its children are restored or created. The .id properties are used and their values are set for each container. The .src represents the DOM element source for the container.

The following becomes true after restoration.

const container = restoreContainerFromJSON(json)
assert(container.id == json.id)
assert(container.children[0].id == json.children[0].id)

Updates are now possible and will propagate to child containers.

container.update({valueA: 'hello', valueB: 'goodbye'})