├── .gitignore
├── LICENSE
├── README.md
├── choo.js
├── examples
├── basic
│ ├── client.js
│ └── package.json
└── title
│ ├── client.js
│ └── package.json
├── html.js
├── http.js
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log*
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Colin Gourlay
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # chooet
2 |
3 | All the goodness of [choo](https://github.com/yoshuawuyts/choo), multi-threaded by [duet](https://github.com/colingourlay/duet).
4 |
5 | ```js
6 | const chooet = require('chooet');
7 | const html = require('chooet/html');
8 |
9 | chooet(choo => {
10 | app = choo();
11 |
12 | app.model({
13 | state: { title: 'Not quite set yet' },
14 | reducers: {
15 | update: (data, state) => ({ title: data })
16 | }
17 | });
18 |
19 | const mainView = (state, prev, send) => html`
20 |
21 | Title: ${state.title}
22 | send('update', value.title)}}>
26 |
27 | `;
28 |
29 | app.router(route => [
30 | route('/', mainView)
31 | ]);
32 |
33 | app.start('body');
34 | });
35 | ```
36 |
--------------------------------------------------------------------------------
/choo.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const barracks = require('barracks');
3 | const duetLocation = require('duet-location');
4 | const duetVirtualDOM = require('duet-virtual-dom');
5 | const sheetRouter = require('sheet-router');
6 |
7 | module.exports = choo;
8 |
9 | // framework for creating sturdy web applications
10 | // null -> fn
11 | function choo (opts) {
12 | opts = opts || {};
13 |
14 | const _store = start._store = barracks();
15 | var _router = start._router = null;
16 | var _defaultRoute = null;
17 | var _update = null
18 | var _routes = null;
19 | var _frame = null;
20 |
21 | _store.use({ onStateChange: render });
22 | _store.use(opts);
23 |
24 | start.router = router;
25 | start.model = model;
26 | start.start = start;
27 | start.use = use;
28 |
29 | return start;
30 |
31 | // start the application
32 | // (str, obj?) -> null
33 | function start (selector, startOpts) {
34 | assert.equal(typeof selector, 'string', 'choo.start: selector should be an string');
35 | startOpts = startOpts || {};
36 | startOpts.vdom = startOpts.vdom || {};
37 |
38 | appInit(function (locationModel) {
39 | _store.model(locationModel);
40 | const createSend = _store.start(startOpts);
41 | _router = start._router = createRouter(_defaultRoute, _routes, createSend);
42 | const state = _store.state({state: {}});
43 | _update = duetVirtualDOM(selector, startOpts.vdom);
44 | _update(_router(state.location.pathname, state));
45 | }, startOpts);
46 | }
47 |
48 | // initial application state model
49 | // (fn, obj) -> null
50 | function appInit (cb, opts) {
51 | var _send;
52 | var _done;
53 |
54 | const subs = {
55 | getRefs: function (send, done) {
56 | _send = send;
57 | _done = done;
58 | }
59 | };
60 |
61 | const reducers = {
62 | setLocation: function setLocation (data, state) {
63 | return { pathname: data.location };
64 | }
65 | };
66 |
67 | duetLocation(function (pathname) {
68 | if (_send != null) {
69 | return _send('location:setLocation', { location: pathname }, _done);
70 | }
71 |
72 | cb({
73 | namespace: 'location',
74 | subscriptions: subs,
75 | reducers: reducers,
76 | state: {
77 | pathname: pathname
78 | }
79 | });
80 |
81 | _done(null);
82 | }, opts.hash === true);
83 | }
84 |
85 | // update the DOM after every state mutation
86 | // (obj, obj, obj, str, fn) -> null
87 | function render (data, state, prev, name, createSend) {
88 | _update(_router(state.location.pathname, state, prev));
89 | }
90 |
91 | // register all routes on the router
92 | // (str?, [fn|[fn]]) -> obj
93 | function router (defaultRoute, routes) {
94 | _defaultRoute = defaultRoute;
95 | _routes = routes;
96 | }
97 |
98 | // create a new model
99 | // (str?, obj) -> null
100 | function model (model) {
101 | _store.model(model);
102 | }
103 |
104 | // register a plugin
105 | // (obj) -> null
106 | function use (hooks) {
107 | assert.equal(typeof hooks, 'object', 'choo.use: hooks should be an object');
108 | _store.use(hooks);
109 | }
110 |
111 | // create a new router with a custom `createRoute()` function
112 | // (str?, obj, fn?) -> null
113 | function createRouter (defaultRoute, routes, createSend) {
114 | var prev = { params: {} };
115 | return sheetRouter(defaultRoute, routes, createRoute);
116 |
117 | function createRoute (routeFn) {
118 | return function (route, inline, child) {
119 | if (typeof inline === 'function') {
120 | inline = wrap(inline, route);
121 | }
122 | return routeFn(route, inline, child);
123 | };
124 |
125 | function wrap (child, route) {
126 | const send = createSend('view: ' + route, true);
127 | return function chooWrap (params, state) {
128 | const nwPrev = prev;
129 | const nwState = prev = Object.assign({}, state, { params: params });
130 | if (opts.freeze !== false) {
131 | Object.freeze(nwState);
132 | }
133 | return child(nwState, nwPrev, send);
134 | };
135 | }
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/examples/basic/client.js:
--------------------------------------------------------------------------------
1 | const chooet = require('../../');
2 | const html = require('../../html');
3 |
4 | chooet(choo => {
5 |
6 | app = choo();
7 |
8 | app.model({
9 | state: { title: 'Not quite set yet' },
10 | reducers: {
11 | update: (data, state) => ({ title: data })
12 | }
13 | });
14 |
15 | const mainView = (state, prev, send) => html`
16 |
17 | Title: ${state.title}
18 | send('update', value.title)}}>
22 |
23 | `;
24 |
25 | app.router(route => [
26 | route('/', mainView)
27 | ]);
28 |
29 | app.start('body');
30 |
31 | });
32 |
33 |
--------------------------------------------------------------------------------
/examples/basic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "basic",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "client.js",
6 | "scripts": {
7 | "start": "budo client.js -p 8080"
8 | },
9 | "keywords": [],
10 | "author": "Colin Gourlay ",
11 | "license": "ISC",
12 | "dependencies": {
13 | "budo": "^8.3.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/title/client.js:
--------------------------------------------------------------------------------
1 | const chooet = require('../../');
2 | const html = require('../../html');
3 | const docTitle = require('duet-document-title');
4 | const docTitleChannel = require('duet-document-title/channel');
5 |
6 | chooet(choo => {
7 |
8 | app = choo();
9 |
10 | app.model({
11 | namespace: 'input',
12 | state: {
13 | title: 'my demo app'
14 | },
15 | reducers: {
16 | update: (data, state) => ({ title: data.payload })
17 | },
18 | effects: {
19 | updated: (data, state, send, done) => {
20 | docTitle(state.title, done);
21 | }
22 | }
23 | });
24 |
25 | const mainView = (state, prev, send) => html`
26 |
27 | ${state.input.title}
28 | Set the title
29 | {
35 | send('input:update', { payload: value.title });
36 | send('input:updated');
37 | }
38 | }}>
39 |
40 | `;
41 |
42 | app.router(route => [
43 | route('/', mainView)
44 | ]);
45 |
46 | app.start('body');
47 |
48 | }, {
49 | channels: [docTitleChannel],
50 | logger: console.log.bind(console) // Log channel messages
51 | });
52 |
53 |
--------------------------------------------------------------------------------
/examples/title/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "title",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "client.js",
6 | "scripts": {
7 | "start": "budo client.js -p 8080"
8 | },
9 | "keywords": [],
10 | "author": "Colin Gourlay ",
11 | "license": "ISC",
12 | "dependencies": {
13 | "budo": "^8.3.0",
14 | "duet-document-title": "^1.0.2"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/html.js:
--------------------------------------------------------------------------------
1 | const hyperx = require('hyperx');
2 | const h = require('virtual-dom/h');
3 |
4 | module.exports = hyperx(h);
5 |
--------------------------------------------------------------------------------
/http.js:
--------------------------------------------------------------------------------
1 | module.exports = require('xhr');
2 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const choo = require('./choo');
2 | const duet = require('duet');
3 | const duetLocationChannel = require('duet-location/channel');
4 | const duetVirtualDOMChannel = require('duet-virtual-dom/channel');
5 |
6 | module.exports = chooet;
7 |
8 | function chooet (cb, opts) {
9 | opts = typeof opts === 'object' ? opts : {};
10 | opts.channels = Array.isArray(opts.channels) ? opts.channels : [];
11 | const channels = opts.channels.concat(duetLocationChannel, duetVirtualDOMChannel);
12 | delete opts.channels;
13 | duet(channels, cb.bind(null, choo), opts);
14 | }
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chooet",
3 | "version": "1.1.0",
4 | "description": "All the goodness of choo, multi-threaded by duet.",
5 | "author": "Colin Gourlay ",
6 | "repository": {
7 | "type": "git",
8 | "url": "git://github.com/colingourlay/chooet.git"
9 | },
10 | "license": "MIT",
11 | "bugs": {
12 | "url": "https://github.com/colingourlay/chooet/issues"
13 | },
14 | "homepage": "https://github.com/colingourlay/chooet#readme",
15 | "keywords": [
16 | "choo",
17 | "duet"
18 | ],
19 | "dependencies": {
20 | "barracks": "^8.3.1",
21 | "duet": "^4.0.1",
22 | "duet-location": "^1.1.0",
23 | "duet-virtual-dom": "^1.1.0",
24 | "hyperx": "^2.0.4",
25 | "sheet-router": "^3.1.0",
26 | "xhr": "^2.2.0",
27 | "virtual-dom": "^2.1.1"
28 | },
29 | "main": "index.js",
30 | "files": [
31 | "choo.js",
32 | "html.js",
33 | "http.js",
34 | "index.js"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------