Skip to content
This repository was archived by the owner on Aug 9, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}