Skip to content
This repository has been archived by the owner on Aug 9, 2024. It is now read-only.

Commit

Permalink
Add Todos widget implemented in Redux
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-kobrzak committed Sep 9, 2015
1 parent 71f404a commit e914a9c
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 0 deletions.
4 changes: 4 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@

<div id="heading-container"></div>

<div id="notes-container"></div>

<a href="/pages/page1.html">Page #1</a>
<a href="/pages/page1.html">Page #2</a>

<!-- Required by Safari -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser-polyfill.min.js"></script>
<script type="text/javascript" src="http://localhost:5000/assets/build.js"></script>

</body>
Expand Down
17 changes: 17 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,22 @@ import Book from './book/book.js';
import Section from './book/section.jsx';
React.render( <Section />, document.getElementById( 'heading-container' ) );

import { createStore } from 'redux';
import { Provider } from 'react-redux';
import todoApp from './flux/reducers';
import Todos from './todos/App.jsx'; // eslint-disable-line no-unused-vars

let store = createStore(todoApp);

let rootElement = document.getElementById('notes-container');
React.render(
// The child must be wrapped in a function
// to work around an issue in React 0.13.
<Provider store={store}>
{() => <Todos />}
</Provider>,
rootElement
);

var book = new Book();
book.logSomething();
24 changes: 24 additions & 0 deletions src/flux/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// action types
export const ADD_TODO = 'ADD_TODO'
export const COMPLETE_TODO = 'COMPLETE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'

// other constants
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
}

// action creators
export function addTodo(text) {
return { type: ADD_TODO, text }
}

export function completeTodo(index) {
return { type: COMPLETE_TODO, index }
}

export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter }
}
42 changes: 42 additions & 0 deletions src/flux/reducers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { combineReducers } from 'redux';
import { ADD_TODO, COMPLETE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters }
from './actions';
const { SHOW_ALL } = VisibilityFilters;

function visibilityFilter(state = SHOW_ALL, action) {
// TODO Switch statements are a no-no
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter;
default:
return state;
}
}

function todos(state = [], action) {
// TODO Switch statements are a no-no
switch (action.type) {
case ADD_TODO:
return [...state, {
text: action.text,
completed: false
}];
case COMPLETE_TODO:
return [
...state.slice(0, action.index),
Object.assign( {}, state[action.index], {
completed: true
}),
...state.slice(action.index + 1)
];
default:
return state;
}
}

const todoApp = combineReducers({
visibilityFilter,
todos
});

export default todoApp;
37 changes: 37 additions & 0 deletions src/todos/AddTodo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react'

export default class AddTodo extends React.Component {

constructor(props) {
super(props)
this.bindInstanceMethods( "handleClick" )
}

bindInstanceMethods( ...methods ) {
methods.forEach(
( method ) => this[ method ] = this[ method ].bind( this )
)
}

render() {
return (
<div>
<input type="text" ref="input" />
<button onClick={this.handleClick}>
Add
</button>
</div>
)
}

handleClick( event ) {
let node = React.findDOMNode( this.refs.input )
let text = node.value.trim()
this.props.onAddClick( text )
node.value = ''
}
}

AddTodo.propTypes = {
onAddClick: React.PropTypes.func.isRequired
}
69 changes: 69 additions & 0 deletions src/todos/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from "react"
import { connect } from 'react-redux'
import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters }
from '../flux/actions'
import AddTodo from './AddTodo.jsx'
import TodoList from './TodoList.jsx'
import Footer from './Footer.jsx'

class App extends React.Component {

render() {
// Injected by connect() call:
const { dispatch, visibleTodos, visibilityFilter } = this.props
return (
<div>
<AddTodo
onAddClick={text =>
dispatch( addTodo( text ) )
} />
<TodoList
todos={visibleTodos}
onTodoClick={index => {
dispatch( completeTodo( index ) )
}} />
<Footer
filter={visibilityFilter}
onFilterChange={nextFilter =>
dispatch( setVisibilityFilter( nextFilter ) )
} />
</div>
)
}
}

App.propTypes = {
visibleTodos: React.PropTypes.arrayOf( React.PropTypes.shape({
text: React.PropTypes.string.isRequired,
completed: React.PropTypes.bool.isRequired
}) ),
visibilityFilter: React.PropTypes.oneOf([
'SHOW_ALL',
'SHOW_COMPLETED',
'SHOW_ACTIVE'
]).isRequired
}

function selectTodos(todos, filter) {
// TODO Switch statements are a no-no
switch ( filter ) {
case VisibilityFilters.SHOW_ALL:
return todos
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter( todo => todo.completed )
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter( todo => !todo.completed )
}
}

// Which props do we want to inject, given the global state?
// Note: use https://github.com/faassen/reselect for better performance.
function select(state) {
return {
visibleTodos: selectTodos( state.todos, state.visibilityFilter ),
visibilityFilter: state.visibilityFilter
}
}

// Wrap the component to inject dispatch and state into it
export default connect( select )( App )
51 changes: 51 additions & 0 deletions src/todos/Footer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react'

export default class Footer extends React.Component {

constructor(props) {
super(props)
}

render() {
return (
<p>
Show:
{' '}
{this.renderFilter('SHOW_ALL', 'All')}
{', '}
{this.renderFilter('SHOW_COMPLETED', 'Completed')}
{', '}
{this.renderFilter('SHOW_ACTIVE', 'Active')}
.
</p>
)
}

renderFilter(filter, name) {
if (filter === this.props.filter) {
return name
}

return (
// Please note the currying technique we use below
<a href="#" onClick={ this.handleClick.bind( this, filter ) }>
{name}
</a>
)
}

handleClick( filter, event ) {
event.preventDefault()
this.props.onFilterChange( filter )
}

}

Footer.propTypes = {
onFilterChange: React.PropTypes.func.isRequired,
filter: React.PropTypes.oneOf([
'SHOW_ALL',
'SHOW_COMPLETED',
'SHOW_ACTIVE'
]).isRequired
}
33 changes: 33 additions & 0 deletions src/todos/Todo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react"

export default class Todo extends React.Component {

render() {
let textDecoration, cursor

if ( this.props.completed ) {
textDecoration = 'line-through'
cursor = 'default'
} else {
textDecoration = 'none'
cursor = 'pointer'
}

return (
<li
onClick={this.props.onClick}
style={{
textDecoration: textDecoration,
cursor: cursor
}}>
{this.props.text}
</li>
);
}
}

Todo.propTypes = {
onClick: React.PropTypes.func.isRequired,
text: React.PropTypes.string.isRequired,
completed: React.PropTypes.bool.isRequired
};
24 changes: 24 additions & 0 deletions src/todos/TodoList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react"
import Todo from './Todo.jsx';

export default class TodoList extends React.Component {
render() {
return (
<ul>
{this.props.todos.map( (todo, index) =>
<Todo {...todo}
key={index}
onClick={ () => this.props.onTodoClick(index) } />
)}
</ul>
);
}
}

TodoList.propTypes = {
onTodoClick: React.PropTypes.func.isRequired,
todos: React.PropTypes.arrayOf( React.PropTypes.shape({
text: React.PropTypes.string.isRequired,
completed: React.PropTypes.bool.isRequired
}).isRequired ).isRequired
};

0 comments on commit e914a9c

Please sign in to comment.