Skip to content

Commit

Permalink
v2 better state-management and routing (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
JRJurman authored Sep 23, 2017
1 parent 0275eb8 commit fd0a21f
Show file tree
Hide file tree
Showing 30 changed files with 9,538 additions and 434 deletions.
2 changes: 2 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ jobs:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}

- run: npm run lint

- run: npm test
46 changes: 0 additions & 46 deletions .eslintrc.yml

This file was deleted.

148 changes: 59 additions & 89 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,16 @@ Here are the different package that make Tram-One possible...

For Rendering:
- [hyperx](https://github.com/substack/hyperx)
- [bel](https://github.com/shama/bel)
- [bel-create-element](https://github.com/JRJurman/bel-create-element)
- [rbel](https://github.com/aaaristo/rbel)
- [nanomorph](https://github.com/choojs/nanomorph)

For Routing:
- [nanorouter](https://github.com/choojs/nanorouter)
- [rlite](https://github.com/chrisdavies/rlite)
- [url-listener](https://github.com/JRJurman/url-listener)

For State Management:
- [nanomorph](https://github.com/choojs/nanomorph)
- [minidux](https://github.com/freeman-lab/minidux)
- [xtend](https://github.com/Raynos/xtend)
- [hover-engine](https://github.com/JRJurman/hover-engine)

While not used in this project, Tram-One is heavily inspired by the
[choo](https://github.com/choojs/choo) view framework.
Expand All @@ -67,6 +66,17 @@ creator, [Yoshua Wuyts](https://github.com/yoshuawuyts).
If you like some of the things here, definitely
[go check out that project](https://github.com/choojs).

## Size
```
┌──────────────────────────────────────────────────┐
│ │
│ Module size: 3.42 KB, Gzipped size: 1.34 KB │
│ │
│ UMD size: 23.75 KB, Gzipped size: 7.96 KB │
│ │
└──────────────────────────────────────────────────┘
```

## Video Tutorial
This video tutorial goes through the process of build a Tram-One web app
from start to finish.
Expand Down Expand Up @@ -136,28 +146,24 @@ const cHtml = Tram.html({
'color-button': colorElement
})

// create a reducer, that handles changing the color of the app
const colorReducer = (state, action) => {
switch(action.type) {
case('SET_COLOR'):
return action.color
default:
return state // you must ALWAYS return the state by default
}
// create a set of actions, that handles changing the color of the app
const colorActions = {
init: () => 'blue',
setColor: (currentColor, newColor) => newColor
}

// home page to load on the main route
const home = (state) => {
const home = (store, actions) => {

// actionCreator that dispatches to the reducer
const onSetColor = (color) => () => {
state.dispatch({type: 'SET_COLOR', color})
actions.setColor(color)
}

// we use cHtml so that we have color-button available in the template
return cHtml`
<div>
I think the best color for this wall is... ${state.color}!
I think the best color for this wall is... ${store.color}!
or maybe it's...
<color-button onclick=${onSetColor('blue')}>blue</color-button>
<color-button onclick=${onSetColor('red')}>red</color-button>
Expand All @@ -166,22 +172,12 @@ const home = (state) => {
`
}

// page to render on the unmatched routes (which by default go to 404)
const noPage = (state) => {
return cHtml`
<div>
<h1>404!</h1>
Sorry pal, no page here...
</div>
`
}

// add routes, by using path matchers with function components
app.addRoute('/', home)
app.addRoute('/404', noPage)

// add reducer, map all state values to 'color', and set the initial value to 'blue'
app.addReducer('color', colorReducer, 'blue')
// add actions and save the state as `color` on the store
app.addActions({color: colorActions})
app.start('.main')
```

Expand All @@ -201,7 +197,7 @@ Tram-One has a simple interface to help build your web app.

### `Tram.html([registry])`
_Reference: [hyperx](https://github.com/substack/hyperx),
[bel](https://github.com/shama/bel),
[bel-create-element](https://github.com/JRJurman/bel-create-element),
[rbel](https://github.com/aaaristo/rbel)_

`Tram.html` returns a function that can be used to transform
Expand Down Expand Up @@ -243,7 +239,7 @@ const html = Tram.html({
'wrap': pageWraper
})

const home = (state) => {
const home = () => {
return html`
<wrap>
This is my shiny app!
Expand Down Expand Up @@ -281,21 +277,13 @@ app.addRoute('/', home)

</details>

### `app.addReducer(key, reducer, state)`
_Reference: [minidux](https://github.com/freeman-lab/minidux)_
### `app.addActions(actionGroups)`
_Reference: [hover-engine](https://github.com/JRJurman/hover-engine)_

`app.addReducer` adds a reducer onto the current instance of Tram.
It takes in three arguments:<br>
`key`, which is where the state will be exposed,<br>
`reducer`, the function that updates state,<br>
`state`, the initial state of the reducer.

Note, `state` here will be exposed in the views as `state[key]`.

The `reducer` should be a function, that takes in `state`, and an `action`.<br>
`state` can be anything you want, a number, object, whatever. At the end of the
reducer, you should ALWAYS return this by default.<br>
`action` should be an object, with a `type` property.
`app.addActions` adds a set of actions that can be triggered in the instance of Tram-One.
It takes in one argument, an object where:<br>
the keys are values that can be pulled in the view<br>
the values are actions that can be triggered in the view<br>

<details>
<summary>
Expand All @@ -307,26 +295,21 @@ Example:
const app = new Tram()
const html = Tram.html()

// in this example, state is a number (the votes)
// in this example, `vote` is a number
// but in a larger app, this could be an object
// with multiple key-value pairs
const counterReducer = (state, action) => {
switch(action.type) {
case('UP'):
return state + 1
case('DOWN'):
return state - 1
default:
return state
}
const voteActions = {
init: () => 0,
up: (vote) => vote + 1,
down: (vote) => vote - 1
}

const home = (state) => {
const home = (state, actions) => {
const upvote = () => {
state.dispatch({type: 'UP'})
actions.up()
}
const downvote = () => {
state.dispatch({type: 'DOWN'})
actions.down()
}

return html`
Expand All @@ -338,21 +321,21 @@ const home = (state) => {
`
}

app.addReducer('votes', counterReducer, 0)
app.addActions({votes: voteActions})
```

</details>

### `app.addRoute(path, page)`
_Reference: [nanorouter](https://github.com/yoshuawuyts/nanorouter)_
_Reference: [rlite](https://github.com/chrisdavies/rlite)_

`app.addRoute` will associate a component with a route.<br>
`path` should be a matchable route for the application. Look up
[nanorouter](https://github.com/yoshuawuyts/nanorouter)
[rlite](https://github.com/chrisdavies/rlite)
to see all the possible options here.<br>
`page` should be a function that takes in a `state` object for the entire app.
`page` should be a function that takes in a `store`, `actions` and `params`.

The state passed into `page` will have any path parameters for the route as well.
The `params` object passed into the `page` function will have any path parameters and query params.

<details>
<summary>
Expand All @@ -364,20 +347,20 @@ Example:
const app = new Tram()
const html = Tram.html()

const homePage = (state) => {
const homePage = () => {
return html`<div>This is my shiny app!</div>`
}

const colorPage = (state) => {
const colorPage = (store, actions, params) => {
const style = `
background: ${state.color};
background: ${params.color};
width: 100px;
height: 100px;
`
return html`<div style=${style}></div>`
}

const noPage = (state) => {
const noPage = () => {
return html`<div>Oh no! We couldn't find what you were looking for</div>`
}

Expand All @@ -388,23 +371,10 @@ app.addRoute('/404', noPage)

</details>

### `app.dispatch(action)`
_Reference: [minidux](https://github.com/freeman-lab/minidux)_

**WARNING: EXPERIMENTAL METHOD**<br>
_This method is currently under discussion:<br>
https://github.com/JRJurman/tram-one/issues/8 ._

`app.dispatch` will dispatch an action to the combined reducers. This should
**only be used outside of components**. When inside of a component, you have
access to `state.dispatch`. `app.dispatch` should only be used when you need to
dispatch an action in testing.
`action` should be an object with a property `type`.

### `app.start(selector, [pathName])`

`app.start` will kick off the app. Once this is called, all the reducers
are combined, and the app is mounted onto the `selector`.<br>
`app.start` will kick off the app. Once this is called the app is mounted onto the
`selector`.<br>
`selector` can be a node or a css selector (which is fed into
`document.querySelector`).<br>
`pathName` can be an initial path, if you don't want to check the browser's
Expand Down Expand Up @@ -449,29 +419,29 @@ app.start('.main')

</details>

### `app.mount(selector, pathName, state)`
### `app.mount(selector, pathName, store, actions)`
**WARNING: INTENDED FOR INTERNAL USE ONLY**

`app.mount` matches a route from `pathName`, passes in a `state` object,
`app.mount` matches a route from `pathName`, passes in a `store` and `actions` object,
and either creates a child div, or updates a child div under `selector`.

This was created to clean up the code in the library, but may be useful for
testing.

**YOU SHOULD NEVER CALL THIS DIRECTLY FOR YOUR APP**

### `app.toNode(pathName, [state])`
### `app.toNode(pathName[, store, actions])`

`app.toNode` returns a HTMLNode of the app for a given route and state. The
function matches a route from `pathName`, and either takes in a `state`, or
uses the default state (that's been created by adding reducers).
`app.toNode` returns a HTMLNode of the app for a given route and store. The
function matches a route from `pathName`, and either takes in a `store`, or
uses the default store (that's been created by adding reducers).

While initially created to clean up the code in the library, this can be useful
if you want to manually attach the HTMLNode that Tram-One builds to whatever.

### `app.toString(pathName, [state])`
### `app.toString(pathName[, store])`

`app.toString` returns a string of the app for a given route and state. It has
`app.toString` returns a string of the app for a given route and store. It has
the same interface at `app.toNode`, and basically just calls `.outerHTML` (or
`toString` on the server) on the node.

Expand Down
24 changes: 16 additions & 8 deletions configs/rollup.esm.config.js → configs/rollup.config.esm.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
const uglify = require('rollup-plugin-uglify')
const babel = require('rollup-plugin-babel')
const filesize = require('rollup-plugin-filesize')

const uglify = require('rollup-plugin-uglify')
const pkg = require('../package.json')

const external = Object.keys(pkg.dependencies)

const plugins = [
babel({
presets: [
'es2015-rollup'
]
['env', {
modules: false,
targets: {
node: '4'
}
}]
],
plugins: ['external-helpers']
}),
uglify(),
filesize()
]

export default {
entry: 'tram-one.js',
input: 'tram-one.js',
external: external,
dest: pkg.main,
format: 'es',
plugins: plugins,
sourceMap: true
output: {
sourcemap: true,
format: 'es',
file: pkg.module
}
}
Loading

0 comments on commit fd0a21f

Please sign in to comment.