├── .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 | 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 | --------------------------------------------------------------------------------