Skip to content

Commit a8e4cbc

Browse files
committed
Add TodoMVC example
1 parent ba5c7f1 commit a8e4cbc

File tree

16 files changed

+563
-1
lines changed

16 files changed

+563
-1
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,3 @@ npm-debug.log*
1717
# Files
1818
dist
1919
test*.*
20-
examples/todomvc

examples/todomvc/.babelrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"presets": ["es2015", "react"],
3+
"env": {
4+
"development": {
5+
"presets": ["react-hmre"]
6+
}
7+
}
8+
}

examples/todomvc/package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "dop-todomvc-example",
3+
"version": "0.0.0",
4+
"description": "dop TodoMVC example",
5+
"scripts": {
6+
"start": "node src/server.js"
7+
},
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/DistributedObjectProtocol/dop.git"
11+
},
12+
"license": "MIT",
13+
"bugs": {
14+
"url": "https://github.com/DistributedObjectProtocol/dop/issues"
15+
},
16+
"homepage": "http://distributedobjectprotocol.org",
17+
"dependencies": {
18+
"babel-polyfill": "^6.3.14",
19+
"classnames": "^2.1.2",
20+
"react": "^0.14.7",
21+
"react-dom": "^0.14.7",
22+
"dop": "^0.21.0"
23+
},
24+
"devDependencies": {
25+
"babel-core": "^6.3.15",
26+
"babel-loader": "^6.2.0",
27+
"babel-preset-es2015": "^6.3.13",
28+
"babel-preset-react": "^6.3.13",
29+
"babel-preset-react-hmre": "^1.1.1",
30+
"babel-register": "^6.3.13",
31+
"cross-env": "^1.0.7",
32+
"enzyme": "^2.2.0",
33+
"expect": "^1.8.0",
34+
"express": "^4.13.3",
35+
"jsdom": "^5.6.1",
36+
"mocha": "^2.2.5",
37+
"node-libs-browser": "^0.5.2",
38+
"raw-loader": "^0.5.1",
39+
"react-addons-test-utils": "^0.14.7",
40+
"style-loader": "^0.12.3",
41+
"todomvc-app-css": "^2.0.1",
42+
"webpack": "^1.9.11",
43+
"webpack-dev-middleware": "^1.2.0",
44+
"webpack-hot-middleware": "^2.9.1"
45+
}
46+
}

examples/todomvc/src/actions.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { collect } from 'dop'
2+
import state from './state'
3+
4+
5+
let todoIdInc = 1
6+
export function addTodo(text) {
7+
if (text.length > 0)
8+
state.todos.push({
9+
text: text,
10+
completed: false,
11+
id: todoIdInc++,
12+
editing: false
13+
})
14+
}
15+
16+
17+
export function deleteTodo(id) {
18+
const index = state.todos.findIndex(todo => todo.id === id)
19+
const collector = collect()
20+
state.todos.splice(index, 1)
21+
collector.emit()
22+
}
23+
24+
25+
export function editTodo(id, text) {
26+
if (text.length === 0)
27+
deleteTodo(id)
28+
else {
29+
const todo = state.todos.filter(todo => todo.id === id)[0]
30+
const collector = collect()
31+
todo.editing = false
32+
if (text !== todo.text)
33+
todo.text = text
34+
collector.emit()
35+
}
36+
}
37+
38+
39+
export function completeTodo(id) {
40+
const todo = state.todos.filter(todo => todo.id === id)[0]
41+
const collector = collect()
42+
todo.completed = !todo.completed
43+
collector.emit()
44+
}
45+
46+
47+
export function completeAll() {
48+
const collector = collect()
49+
const areAllMarked = state.todos.every(todo => todo.completed)
50+
state.todos.forEach(todo => todo.completed=!areAllMarked)
51+
collector.emit()
52+
}
53+
54+
55+
export function changeFilter(filter) {
56+
state.selectedFilter = filter
57+
}
58+
59+
60+
export function clearCompleted() {
61+
const collector = collect()
62+
const completeds = state.todos
63+
.filter(todo => todo.completed === true)
64+
.map(todo => todo.id)
65+
.forEach(deleteTodo)
66+
collector.emit()
67+
}
68+
69+
70+
export function editingTodo(id) {
71+
const todo = state.todos.filter(todo => todo.id === id)[0]
72+
todo.editing = !todo.editing
73+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React, { Component, PropTypes } from 'react'
2+
import Header from './Header'
3+
import Todos from './Todos'
4+
import Footer from './Footer'
5+
6+
export default function App() {
7+
return (
8+
<div>
9+
<Header />
10+
<Todos />
11+
<Footer />
12+
</div>
13+
)
14+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React, { PropTypes, Component } from 'react'
2+
import classnames from 'classnames'
3+
import { createObserver } from 'dop'
4+
import state from '../state'
5+
import { clearCompleted, changeFilter } from '../actions'
6+
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants'
7+
8+
const FILTER_TITLES = {
9+
[SHOW_ALL]: 'All',
10+
[SHOW_ACTIVE]: 'Active',
11+
[SHOW_COMPLETED]: 'Completed'
12+
}
13+
14+
export default class Footer extends Component {
15+
16+
componentWillMount() {
17+
const observer = createObserver(mutations => {
18+
this.forceUpdate()
19+
})
20+
observer.observe(state, 'itemsLeftCount')
21+
observer.observe(state, 'selectedFilter')
22+
observer.observe(state, 'completedCount')
23+
}
24+
25+
26+
shouldComponentUpdate() {
27+
return false
28+
}
29+
30+
31+
render() {
32+
return <FooterTemplate
33+
activeCount={state.itemsLeftCount}
34+
completedCount={state.completedCount}
35+
selectedFilter={state.selectedFilter}
36+
onChangeFilter={changeFilter}
37+
onClearCompleted={clearCompleted} />
38+
}
39+
}
40+
41+
42+
43+
44+
45+
function FooterTemplate({ activeCount, completedCount, selectedFilter, onChangeFilter, onClearCompleted }) {
46+
47+
const itemWord = activeCount === 1 ? 'item' : 'items'
48+
49+
return (
50+
<footer className="footer">
51+
<span className="todo-count">
52+
<strong>{activeCount || 'No'}</strong> {itemWord} left
53+
</span>
54+
<ul className="filters">
55+
{[ SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED ].map(filter =>
56+
<li key={filter}>
57+
<a className={classnames({ selected: filter === selectedFilter })}
58+
style={{ cursor: 'pointer' }}
59+
onClick={() => onChangeFilter(filter)}>
60+
{FILTER_TITLES[filter]}
61+
</a>
62+
</li>
63+
)}
64+
</ul>
65+
{ completedCount === 0 ? null : <button
66+
className="clear-completed"
67+
onClick={onClearCompleted}>
68+
Clear completed
69+
</button>
70+
}
71+
</footer>
72+
)
73+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React, { Component, PropTypes } from 'react'
2+
import { createObserver } from 'dop'
3+
import state from '../state'
4+
import { addTodo, completeAll } from '../actions'
5+
import TodoInput from './TodoInput'
6+
7+
8+
export default class Header extends Component {
9+
10+
componentWillMount() {
11+
const observer = createObserver(mutations => {
12+
this.forceUpdate()
13+
})
14+
observer.observe(state, 'areAllItemsCompleted')
15+
}
16+
17+
shouldComponentUpdate() {
18+
return false
19+
}
20+
21+
render() {
22+
return <HeaderTemplate
23+
onSave={addTodo}
24+
onCompleteAll={completeAll}
25+
areAllItemsCompleted={state.areAllItemsCompleted}
26+
/>
27+
}
28+
}
29+
30+
31+
32+
33+
function HeaderTemplate({ onSave, onCompleteAll, areAllItemsCompleted }) {
34+
return (
35+
<header className="header">
36+
<h1>todos</h1>
37+
<TodoInput
38+
newTodo
39+
placeholder="What needs to be done?"
40+
onSave={onSave}/>
41+
<input className="toggle-all"
42+
type="checkbox"
43+
checked={areAllItemsCompleted}
44+
onChange={onCompleteAll} />
45+
</header>
46+
)
47+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React, { Component, PropTypes } from 'react'
2+
import classnames from 'classnames'
3+
4+
class TodoInput extends Component {
5+
constructor(props, context) {
6+
super(props, context)
7+
this.state = {
8+
text: this.props.text || ''
9+
}
10+
}
11+
12+
handleSubmit(e) {
13+
const text = e.target.value.trim()
14+
if (e.which === 13) {
15+
this.props.onSave(text)
16+
if (this.props.newTodo) {
17+
this.setState(()=>{
18+
return { text: '' }
19+
})
20+
}
21+
}
22+
}
23+
24+
handleChange(e) {
25+
let text = e.target.value.trim()
26+
this.setState(()=>{
27+
return { text: text }
28+
})
29+
}
30+
31+
handleBlur(e) {
32+
if (!this.props.newTodo) {
33+
this.props.onSave(e.target.value)
34+
}
35+
}
36+
37+
render() {
38+
// console.log( 'TodoInput' );
39+
return (
40+
<input className={
41+
classnames({
42+
edit: this.props.editing,
43+
'new-todo': this.props.newTodo
44+
})}
45+
type="text"
46+
placeholder={this.props.placeholder}
47+
autoFocus="true"
48+
value={this.state.text}
49+
onBlur={this.handleBlur.bind(this)}
50+
onChange={this.handleChange.bind(this)}
51+
onKeyDown={this.handleSubmit.bind(this)} />
52+
)
53+
}
54+
}
55+
56+
TodoInput.propTypes = {
57+
onSave: PropTypes.func.isRequired,
58+
text: PropTypes.string,
59+
placeholder: PropTypes.string,
60+
editing: PropTypes.bool,
61+
newTodo: PropTypes.bool
62+
}
63+
64+
export default TodoInput

0 commit comments

Comments
 (0)