app/modules/todos/TodoApp.component.js
'use strict';
// Styles
import './TodoApp.scss';
// Dependencies
import React from 'react';
import { connect } from 'react-redux';
import classNames from 'classnames';
// Actions
import {
addTodo,
removeTodo,
toggleTodo,
clearTodo
} from './reducers/todos.reducer';
import {
todoFilters,
setTodoFilter,
getFilteredTodos
} from './reducers/todoFilter.reducer';
// Presentational Components
/**
* Generate a clickable todo item with a label and an delete button
* @param {!Object} props
* react props object
* @param {!boolean} props.completed
* whether the todo is completed or not
* @param {!string} props.label
* the todo label
* @param {!function} props.onClick
* the function to perform when the todo label is clicked, here {@link toggleTodo}
* @param {!function} props.onButtonClick
* the function to perform when the todo button is clicked, here {@link removeTodo}
* @return {ReactDOM}
* generate `<li class="todoList_item"/>` markup
*/
export const Todo = (
{
completed,
label,
onClick,
onButtonClick
}
) =>
<li className={`todoList_item todoList_item-${completed ? 'completed' : 'pending'}`}>
<span className="todoList_itemLabel"
onClick={onClick}>{label}</span>
<button className="todoList_itemButton"
onClick={onButtonClick}>+</button>
</li>;
/**
* Generate a list of todos
* @param {!Object} props
* react props object
* @param {!Array<todoObj>} props.filteredTodos
* pre-filtered list of todos depending on the actual selected filter
* @param {!function(id:number)} props.onTodoClick
* function to perform when a todo label is clicked, here {@link toggleTodo}
* @param {!function(id:number)} props.onTodoButtonClick
* function to perform when a todo button is clicked, here {@link removeTodo}
* @return {ReactDOM}
* generate `<ul class="todoList"/>` markup
*/
export const TodoList = (
{
filteredTodos,
onTodoClick,
onTodoButtonClick
}
) =>
<ul className="todoList">
{
filteredTodos.map(
todo =>
<Todo key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
onButtonClick={() => onTodoButtonClick(todo.id)}/>
)
}
</ul>;
/**
* Generate a form to submit a new todo
* @param {!Object} props
* react props object
* @param {!function(label:string)} props.onTodoFormSubmit
* function to perform on form submit
* @return {ReactDOM}
* generate `<form class="todoForm"/>` markup
*/
export const TodoForm = ({ onSubmit }) => {
let _label = '';
return (
<form className="todoForm"
onSubmit={
event => {
event.preventDefault();
_label.value.trim() && onSubmit(_label.value.trim());
_label.value = '';
}
}>
<input className="todoForm_label"
type="text"
placeholder="What's need to be done today ?"
required
ref={input => _label = input}/>
<button className="todoForm_submit">add</button>
</form>
);
};
/**
* Generate a link to change filter value
* @param {!Object} props
* react props object
* @param {!boolean} props.active
* whether the link is already active or not
* @param {!string} props.filter
* constant representing the filter value
* @param {!function(filter:string)} props.onClick
* function to perform when link is clicked, here {@link setTodoFilter}
* @return {ReactDOM}
* generate `<a class="filterList_item">` markup
*/
export const FilterLink = (
{
active,
filter,
onClick
}
) =>
<a className={classNames('filterList_item', {'filterList_item-active' : active})}
href={`#${filter}`}
onClick={
event => {
event.preventDefault();
!active && onClick(filter);
}
}>{filter.replace(/SHOW_/, '')}</a>;
/**
* Generate a list of todo filter links
* @param {!Object} props
* react props object
* @param {!Array<string>} props.filterList
* list of all available filters
* @param {!string} props.currentFilter
* constant representing the currently active filter in the list
* @param {!function()} props.onLinkClick
* function to perform when a filter link is clicked
* @return {ReactDOM}
* generate `<div class="filterList" />` markup
*/
export const TodoFilterLinks = (
{
filterList,
currentFilter,
onLinkClick
}
) =>
<div className="filterList">
<span class="filterList_label">see:</span>
{
filterList.map(
(filter, index) =>
<FilterLink key={index}
filter={filter}
active={filter === currentFilter}
onClick={onLinkClick}/>
)
}
</div>;
/**
* Generate a footer for the todo list
* @param {!Object} props
* react props object
* @param {!Array<todoObj>} props.todos
* list of all todos
* @param {!function()} props.onTodoClearClick
* function to perform when the clear button is clicked
* @return {ReactDOM}
* generate `<p class="todoFooter" />` markup
*/
export const TodoFooter = (
{
todos,
onTodoClearClick
}
) => {
let completed = todos.filter(todo => todo.completed).length,
total = todos.length,
text = total ? (total === completed ? 'all done, good job!' : `${completed}/${total} done`) : 'no todo yet';
return (
<p className="todoFooter">
<span className="todoFooter_count">{text}</span>
<a className={classNames('todoFooter_clear', {'todoFooter_clear-hide' : !completed})}
onClick={(event) => {
event.preventDefault();
completed && onTodoClearClick();
}}>clear completed</a>
</p>
);
};
// Container Components
/**
* Generate the whole todo application
* @param {!Object} props
* react props object
* @param {!Array<todoObj>} props.todos
* list of all available todos
* @param {!string} props.filter
* current active filter
* @param {!Array<string>} props.filterList
* list of all todo filters
* @param {!function} props.onTodoFormSubmit
* function to perform when the todo form is submitted
* @param {!function} props.onTodoClick
* function to perform when the todo label is clicked
* @param {!function} props.onTodoButtonClick
* function to perform when the todo button is clicked
* @param {!function} props.onTodoClearClick
* function to perform when the clear button is clicked
* @param {!function} props.onLinkClick
* function to perform when a filter link is clicked
* @return {ReactDOM}
* generate `<section class="todoApp" />` markup
*/
export const TodoApp = (
{
todos,
filter,
filterList,
onTodoFormSubmit,
onTodoClick,
onTodoButtonClick,
onTodoClearClick,
onLinkClick
}
) =>
<section className="todoApp">
<TodoForm onSubmit={onTodoFormSubmit}/>
<TodoFilterLinks filterList={filterList}
currentFilter={filter}
onLinkClick={onLinkClick}/>
<TodoList filteredTodos={getFilteredTodos(todos, filter)}
onTodoClick={onTodoClick}
onTodoButtonClick={onTodoButtonClick}/>
<TodoFooter todos={todos} onTodoClearClick={onTodoClearClick}/>
</section>;
const MapStateToProps = state => ({
todos : state.todos,
filter : state.todoFilter,
filterList : Object.keys(todoFilters)
});
const MapDispatchToProps = dispatch => ({
onTodoFormSubmit : label => dispatch(addTodo(label)),
onTodoClick : id => dispatch(toggleTodo(id)),
onTodoButtonClick : id => dispatch(removeTodo(id)),
onTodoClearClick : () => dispatch(clearTodo()),
onLinkClick : filter => dispatch(setTodoFilter(filter))
});
/**
* connect react component to redux state
* @return {ReactDOM}
* generate connected `<section class="todoApp" />` markup
*/
export default connect(
MapStateToProps,
MapDispatchToProps
)(TodoApp);