├── .babelrc ├── .gitignore ├── README.md ├── devServer.js ├── index.html ├── package.json ├── src ├── components │ ├── counter.js │ ├── list-counters-fancy.js │ ├── list-counters.js │ └── two-counters.js ├── index.js └── start.js ├── webpack.config.dev.js └── webpack.config.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Elmish React attempt with Mini-signals 2 | ===================== 3 | 4 | This is my personal attempt with [Elm Architecture](https://github.com/evancz/elm-architecture-tutorial/) 5 | 6 | Inspired in Dan Abramov's [attempt](https://github.com/gaearon/react-elmish-example) 7 | 8 | ## Running 9 | 10 | It's up on [Github Pages](http://paparga.github.io/elmish-react). 11 | 12 | You can also 13 | 14 | ``` 15 | git clone https://github.com/paparga/elmish-react.git 16 | cd elmish-react 17 | npm install 18 | npm start 19 | open http://localhost:3000 20 | ``` 21 | 22 | ## License 23 | 24 | MIT 25 | -------------------------------------------------------------------------------- /devServer.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | var config = require('./webpack.config.dev'); 5 | 6 | var app = express(); 7 | var compiler = webpack(config); 8 | 9 | app.use(require('webpack-dev-middleware')(compiler, { 10 | noInfo: true, 11 | publicPath: config.output.publicPath 12 | })); 13 | 14 | 15 | app.get('*', function(req, res) { 16 | res.sendFile(path.join(__dirname, 'index.html')); 17 | }); 18 | 19 | app.listen(3000, 'localhost', function(err) { 20 | if (err) { 21 | console.log(err); 22 | return; 23 | } 24 | 25 | console.log('Listening at http://localhost:3000'); 26 | }); 27 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Elmish-React 5 | 6 | 7 |

One Counter

8 |
9 |

Two Counters

10 |
11 |

List of counters

12 |
13 |

List of counters fancy

14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elmish-react", 3 | "version": "0.0.5", 4 | "description": "My elmish React attempt with mini-signals", 5 | "main": "dist/bundle.js", 6 | "dependencies": { 7 | "mini-signals": "^1.1.0", 8 | "react": "^0.14.2", 9 | "react-dom": "^0.14.2" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "^5.4.7", 13 | "babel-loader": "^5.1.2", 14 | "express": "^4.13.3", 15 | "rimraf": "^2.4.3", 16 | "webpack": "^1.9.6", 17 | "webpack-dev-middleware": "^1.2.0" 18 | }, 19 | "keywords": [ 20 | "react", 21 | "elm", 22 | "architecture", 23 | "example" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/paparga/elmish-react.git" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/paparga/elmish-react/issues" 31 | }, 32 | "scripts": { 33 | "clean": "rimraf dist", 34 | "build:webpack": "NODE_ENV=production webpack --config webpack.config.prod.js", 35 | "build": "npm run clean && npm run build:webpack", 36 | "publish": "npm run build && mkdir -p dist && cp index.html dist && cd dist && git init && git commit --allow-empty -m 'update site' && git checkout -b gh-pages && touch .nojekyll && git add . && git commit -am 'update site' && git push git@github.com:paparga/elmish-react gh-pages --force", 37 | "start": "node devServer.js" 38 | 39 | }, 40 | "author": "Pablo Parga", 41 | "license": "MIT" 42 | } 43 | -------------------------------------------------------------------------------- /src/components/counter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const INCREMENT = 'INCREMENT' 4 | const DECREMENT = 'DECREMENT' 5 | 6 | export function update(actions, model){ 7 | let action = actions.shift() 8 | switch (action) { 9 | case INCREMENT: 10 | return model + 1 11 | case DECREMENT: 12 | return model - 1 13 | default: 14 | return model 15 | } 16 | } 17 | 18 | export function view({dispatch, model}){ 19 | 20 | const style = { fontSize: '20px', 21 | fontFamily: 'monospace', 22 | display: 'inline-block', 23 | width: '50px', 24 | textAlign: 'center' 25 | } 26 | 27 | return ( 28 |
29 | 30 |
{model}
31 | 32 |
33 | ) 34 | } 35 | 36 | export function viewWithRemove({context, model}){ 37 | 38 | const style = { fontSize: '20px', 39 | fontFamily: 'monospace', 40 | display: 'inline-block', 41 | width: '50px', 42 | textAlign: 'center' 43 | } 44 | 45 | return ( 46 |
47 | 48 |
{model}
49 | 50 |
51 | 52 |
53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /src/components/list-counters-fancy.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as Counter from './counter' 3 | import signals from 'mini-signals' 4 | 5 | const INSERT = 'INSERT' 6 | const REMOVE = 'REMOVE' 7 | const MODIFY = 'MODIFY' 8 | 9 | const forward = (dispatch, actions) => (child) => { 10 | dispatch(actions.concat(child)) 11 | } 12 | 13 | export function update(actions, model){ 14 | let action = actions.shift() 15 | if (action == undefined) { 16 | return model 17 | } 18 | switch (action.type || action) { 19 | case INSERT: 20 | let counter = {id: model.nextId, model: 0} 21 | let nextId = model.nextId + 1 22 | model.counters.push(counter) 23 | return {counters: model.counters, nextId } 24 | 25 | case REMOVE: 26 | for (var i = 0; i < model.counters.length; i++) { 27 | if (action.id == model.counters[i].id) { 28 | model.counters.splice(i, 1); 29 | } 30 | } 31 | return model 32 | 33 | case MODIFY: 34 | for (var i = 0; i < model.counters.length; i++) { 35 | if (action.id == model.counters[i].id) { 36 | let counterModel = model.counters[i].model 37 | model.counters[i].model = Counter.update(actions, counterModel) 38 | } 39 | } 40 | return model 41 | 42 | default: 43 | return model 44 | } 45 | } 46 | 47 | export function view({dispatch, model}){ 48 | let counterList = model.counters.map((counter) => { 49 | 50 | let context = { 51 | actions: forward(dispatch,[{type: MODIFY, id: counter.id}]), 52 | remove: forward(dispatch,[{type: REMOVE, id: counter.id}])} 53 | 54 | return
55 | 56 |
57 | }) 58 | return ( 59 |
60 | {counterList} 61 | 62 |
63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /src/components/list-counters.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as Counter from './counter' 3 | import signals from 'mini-signals' 4 | 5 | const INSERT = 'INSERT' 6 | const REMOVE = 'REMOVE' 7 | const MODIFY = 'MODIFY' 8 | 9 | const forward = (dispatch, actions) => (child) => { 10 | dispatch(actions.concat(child)) 11 | } 12 | 13 | export function update(actions, model){ 14 | let action = actions.shift() 15 | if (action == undefined) { 16 | return model 17 | } 18 | switch (action.type || action) { 19 | case INSERT: 20 | let counter = {id: model.nextId, model: 0} 21 | let nextId = model.nextId + 1 22 | model.counters.push(counter) 23 | return {counters: model.counters, nextId } 24 | 25 | case REMOVE: 26 | model.counters.pop() 27 | return model 28 | 29 | case MODIFY: 30 | for (var i = 0; i < model.counters.length; i++) { 31 | if (action.id == model.counters[i].id) { 32 | let counterModel = model.counters[i].model 33 | model.counters[i].model = Counter.update(actions, counterModel) 34 | } 35 | } 36 | return model 37 | 38 | default: 39 | return model 40 | } 41 | } 42 | 43 | export function view({dispatch, model}){ 44 | let counterList = model.counters.map((counter) => { 45 | return
46 | 47 |
48 | }) 49 | return ( 50 |
51 | {counterList} 52 | 53 | 54 |
55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/components/two-counters.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as Counter from './counter' 3 | import signals from 'mini-signals' 4 | 5 | const RESET = 'RESET' 6 | const TOP = 'TOP' 7 | const BOTTOM = 'BOTTOM' 8 | 9 | const forward = (dispatch, actions) => (child) => { 10 | dispatch(actions.concat(child)) 11 | } 12 | 13 | export function update(actions, model){ 14 | 15 | let action = actions.shift() 16 | 17 | switch (action) { 18 | case RESET: 19 | return {topCounter: 0, bottomCounter: 0} 20 | case TOP: 21 | return {topCounter: Counter.update(actions, model.topCounter), 22 | bottomCounter: model.bottomCounter} 23 | case BOTTOM: 24 | return {topCounter: model.topCounter, 25 | bottomCounter: Counter.update(actions, model.bottomCounter)} 26 | default: 27 | return model 28 | } 29 | } 30 | 31 | export function view({dispatch, model}){ 32 | return ( 33 |
34 | 35 | 36 | 37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as Counter from './components/counter' 2 | import * as TwoCounters from './components/two-counters' 3 | import * as ListCounters from './components/list-counters' 4 | import * as ListCountersFancy from './components/list-counters-fancy' 5 | import start from './start' 6 | 7 | const oneCounterModel = 0 8 | const listCounterModel = {counters: [], nextId: 1} 9 | const listCounterFancyModel = {counters: [], nextId: 10000000} 10 | const twoCountersModel = {topCounter: 0, bottomCounter: 0} 11 | 12 | start(oneCounterModel, Counter, 'one-counter') 13 | 14 | start(twoCountersModel, TwoCounters, 'two-counters') 15 | 16 | start(listCounterModel, ListCounters, 'list-counters') 17 | 18 | start(listCounterFancyModel, ListCountersFancy, 'list-counters-fancy') 19 | -------------------------------------------------------------------------------- /src/start.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import signals from 'mini-signals' 4 | 5 | export default function start(model, Component, nodeId) { 6 | 7 | const signal = new signals() 8 | 9 | const node = document.getElementById(nodeId) 10 | 11 | function redraw(actions){ 12 | model = Component.update(actions,model) 13 | render( 14 | , 15 | node 16 | ) 17 | } 18 | 19 | signal.add(redraw) 20 | signal.dispatch([]) 21 | 22 | } 23 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var webpack = require('webpack'); 4 | 5 | module.exports = { 6 | devtool: 'eval', 7 | entry: [ 8 | './src/index' 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'dist'), 12 | filename: 'bundle.js', 13 | publicPath: '/' 14 | }, 15 | plugins: [ 16 | new webpack.NoErrorsPlugin() 17 | ], 18 | module: { 19 | loaders: [{ 20 | test: /\.js$/, 21 | loaders: ['babel'], 22 | include: path.join(__dirname, 'src') 23 | }] 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var webpack = require('webpack'); 4 | 5 | module.exports = { 6 | devtool: 'source-map', 7 | entry: [ 8 | './src/index' 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'dist'), 12 | filename: 'bundle.js', 13 | publicPath: '/' 14 | }, 15 | plugins: [ 16 | new webpack.optimize.OccurenceOrderPlugin(), 17 | new webpack.DefinePlugin({ 18 | 'process.env': { 19 | 'NODE_ENV': JSON.stringify('production') 20 | } 21 | }), 22 | new webpack.optimize.UglifyJsPlugin({ 23 | compressor: { 24 | warnings: false 25 | } 26 | }) 27 | ], 28 | module: { 29 | loaders: [{ 30 | test: /\.js$/, 31 | loaders: ['babel'], 32 | include: path.join(__dirname, 'src') 33 | }] 34 | } 35 | }; 36 | --------------------------------------------------------------------------------