<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<!DOCTYPE html><html class=''>
<head><script src='//production-assets.codepen.io/assets/editor/live/console_runner-079c09a0e3b9ff743e39ee2d5637b9216b3545af0de366d4b9aad9dc87e26bfd.js'></script><script src='//production-assets.codepen.io/assets/editor/live/events_runner-73716630c22bbc8cff4bd0f07b135f00a0bdc5d14629260c3ec49e5606f98fdd.js'></script><script src='//production-assets.codepen.io/assets/editor/live/css_live_reload_init-2c0dc5167d60a5af3ee189d570b1835129687ea2a61bee3513dee3a50c115a77.js'></script><meta charset='UTF-8'><meta name="robots" content="noindex"><link rel="shortcut icon" type="image/x-icon" href="//production-assets.codepen.io/assets/favicon/favicon-8ea04875e70c4b0bb41da869e81236e54394d63638a1ef12fa558a4a835f1164.ico" /><link rel="mask-icon" type="" href="//production-assets.codepen.io/assets/favicon/logo-pin-f2d2b6d2c61838f7e76325261b7195c27224080bc099486ddd6dccb469b8e8e6.svg" color="#111" /><link rel="canonical" href="https://codepen.io/subpopular/pen/rxdrXz?depth=everything&order=popularity&page=14&q=redux&show_forks=false" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel='stylesheet prefetch' href='https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css'><link rel='stylesheet prefetch' href='//fonts.googleapis.com/icon?family=Material+Icons'>
<style class="cp-pen-styles">* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
font-size: 16px;
line-height: 24px;
background: #EEE;
minHeight: 100vh;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
button {
display: inline-block;
border: 0;
background: #333;
margin: 0;
color: #FFF;
height: 36px;
padding: 0 12px;
text-transform: uppercase;
font-size: 16px;
line-height: 24px;
border-radius: 3px;
outline: none;
}
.material-icons {
background: transparent;
color: #000;
padding: 8px;
height: auto;
border-radius: 50%;
}
.material-icons:hover {
background: rgba(0, 0, 0, 0.1);
}
.TodoApp {
width: 600px;
margin: 60px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
.TodoApp__filters {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
padding: 16px;
background: transparent;
}
.TodoApp__filter__btn, .TodoApp__filter__btn--active {
background: transparent;
color: #999;
text-transform: none;
font-size: 14px;
padding: 0;
width: 90px;
height: auto;
border: 1px solid #999;
}
.TodoApp__filter__btn:nth-child(1), .TodoApp__filter__btn--active:nth-child(1) {
border-radius: 3px 0 0 3px;
border-right: 0;
}
.TodoApp__filter__btn:nth-child(2), .TodoApp__filter__btn--active:nth-child(2) {
border-radius: 0;
border-right: 0;
}
.TodoApp__filter__btn:nth-child(3), .TodoApp__filter__btn--active:nth-child(3) {
border-radius: 0 3px 3px 0;
}
.TodoApp__filter__btn--active {
background: #999;
color: #FFF;
}
.TodoApp__form {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
margin-bottom: 0px;
z-index: 9;
position: relative;
}
.TodoApp__form__input {
border: 0;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
padding: 24px 16px;
display: block;
font-size: 24px;
outline: none;
background: #FFF;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
}
.TodoApp__form__add-btn {
height: auto;
padding: 0 24px;
margin-left: 8px;
}
.TodoApp__todo, .TodoApp__todo--completed {
background: #fdfdfd;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 16px 12px;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15);
}
.TodoApp__todo + .TodoApp__todo, .TodoApp__todo--completed + .TodoApp__todo, .TodoApp__todo + .TodoApp__todo--completed, .TodoApp__todo--completed + .TodoApp__todo--completed {
border-top: 1px solid #EEE;
}
.TodoApp__todo.editing, .editing.TodoApp__todo--completed {
padding: 0;
}
.TodoApp__todo:hover .TodoApp__todo__delete-btn, .TodoApp__todo--completed:hover .TodoApp__todo__delete-btn {
opacity: 1;
}
.TodoApp__todo--completed .TodoApp__todo__complete-btn {
color: green;
}
.TodoApp__todo__edit-input {
padding: 20px 24px 22px 24px;
font-size: 20px;
line-height: 24px;
margin: 0 0 0 0px;
width: 100%;
font-weight: 300;
background: #FFF9C4;
border: 0;
outline: none;
}
.TodoApp__todo__label {
font-size: 20px;
margin: 0 12px;
color: #333;
font-weight: 300;
}
.TodoApp__todo--completed .TodoApp__todo__label {
color: #CCC;
text-decoration: line-through;
}
.TodoApp__todo__primary {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.TodoApp__todo__complete-btn {
border: 1px solid #CCC;
padding: 4px;
color: transparent;
}
.TodoApp__todo__complete-btn:hover {
background: transparent;
}
.editing .TodoApp__todo__complete-btn {
display: none;
}
.TodoApp__todo__delete-btn {
-webkit-transition: all .1s ease-out;
transition: all .1s ease-out;
padding: 4px;
color: #777;
opacity: 0;
}
.TodoApp__todo__delete-btn:hover {
background: transparent;
color: red;
}
.editing .TodoApp__todo__delete-btn {
display: none;
}
::-webkit-input-placeholder {
font-weight: 100;
color: #BBB;
}
::-moz-placeholder {
/* Firefox 19+ */
font-weight: 100;
color: #BBB;
}
</style></head><body>
<div id="root"></div>
<script src='//production-assets.codepen.io/assets/common/stopExecutionOnTimeout-b2a7b3fe212eaa732349046d8416e00a9dec26eb7fd347590fbced3ab38af52e.js'></script><script src='https://codepen.io/chriscoyier/pen/yIgqi.js'></script><script src='https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react.min.js'></script><script src='https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react-dom.min.js'></script><script src='https://cdnjs.cloudflare.com/ajax/libs/redux/3.0.6/redux.min.js'></script><script src='https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.0.6/react-redux.min.js'></script><script src='https://cdnjs.cloudflare.com/ajax/libs/immutable/3.7.6/immutable.min.js'></script>
<script >'use strict';
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var _Immutable = Immutable;
var Map = _Immutable.Map;
var List = _Immutable.List;
var fromJS = _Immutable.fromJS;
var _React = React;
var Component = _React.Component;
var _Redux = Redux;
var createStore = _Redux.createStore;
var combineReducers = _Redux.combineReducers;
var initialState = {
todos: [],
visibilityFilter: 'SHOW_ALL'
};
/**
*
* REDUCERS
*
*/
var addTodo = function addTodo(state, action) {
var todos = state.get('todos');
var newTodo = fromJS({
id: getTodoId(todos),
text: action.text,
completed: false
});
return state.set('todos', todos.push(newTodo));
};
var deleteTodo = function deleteTodo(state, action) {
var todoIndex = state.get('todos').findIndex(function (t) {
return t.get('id') === action.id;
});
return state.deleteIn(['todos', todoIndex]);
};
var editTodo = function editTodo(state, action) {
var todos = state.get('todos');
var todoIndex = todos.findIndex(function (t) {
return t.get('id') === action.id;
});
return state.setIn(['todos', todoIndex, 'text'], action.text);
};
var toggleTodo = function toggleTodo(state, action) {
var todos = state.get('todos');
var _todos$findEntry = todos.findEntry(function (t) {
return t.get('id') === action.id;
});
var todoIndex = _todos$findEntry[0];
var todo = _todos$findEntry[1];
return state.setIn(['todos', todoIndex], todo.set('completed', !todo.get('completed')));
};
var setFilter = function setFilter(state, action) {
return action.filter ? state.set('visibilityFilter', action.filter) : state;
};
var todoList = function todoList() {
var state = arguments.length <= 0 || arguments[0] === undefined ? initialState : arguments[0];
var action = arguments[1];
if (!Map.isMap(state) && !List.isList(state)) state = Immutable.fromJS(state);
var handlers = {
ADD_TODO: addTodo,
DELETE_TODO: deleteTodo,
EDIT_TODO: editTodo,
TOGGLE_TODO: toggleTodo,
SET_FILTER: setFilter
};
return handlers[action.type] ? handlers[action.type](state, action) : state;
};
/**
*
* COMPONENTS
*
*/
var Todo = function Todo(_ref) {
var todo = _ref.todo;
var editing = _ref.editing;
var todoEditInputRef = _ref.todoEditInputRef;
var toggleTodo = _ref.toggleTodo;
var beginEditing = _ref.beginEditing;
var endEditing = _ref.endEditing;
var deleteTodo = _ref.deleteTodo;
return React.createElement(
'div',
{
className: (!todo.completed ? 'TodoApp__todo' : 'TodoApp__todo--completed') + (editing ? ' editing' : ''),
onDoubleClick: function onDoubleClick(e) {
return beginEditing(todo.id);
} },
React.createElement(
'div',
{ className: 'TodoApp__todo__primary' },
React.createElement(
'button',
{ className: 'material-icons TodoApp__todo__complete-btn', onClick: function onClick() {
return toggleTodo(todo.id);
} },
'done'
),
editing ? React.createElement('input', {
className: 'TodoApp__todo__edit-input',
ref: todoEditInputRef,
onKeyUp: function onKeyUp(e) {
if (e.keyCode === 13) endEditing(todo.id);
},
onBlur: function onBlur(e) {
return endEditing(todo.id);
},
defaultValue: todo.text }) : React.createElement(
'div',
{ className: 'TodoApp__todo__label' },
todo.text
)
),
React.createElement(
'button',
{ className: 'material-icons TodoApp__todo__delete-btn', onClick: function onClick() {
return deleteTodo(todo.id);
} },
'close'
)
);
};
var VisibilityFilterButton = function VisibilityFilterButton(_ref2) {
var filter = _ref2.filter;
var label = _ref2.label;
var active = _ref2.active;
var setFilter = _ref2.setFilter;
return React.createElement(
'button',
{
className: active(filter) ? 'TodoApp__filter__btn--active' : 'TodoApp__filter__btn',
onClick: function onClick() {
return setFilter(filter);
} },
label
);
};
var TodoApp = function (_Component) {
_inherits(TodoApp, _Component);
function TodoApp(props) {
_classCallCheck(this, TodoApp);
var _this = _possibleConstructorReturn(this, _Component.call(this, props));
_this.state = {
editing: null
};
return _this;
}
TodoApp.prototype.render = function render() {
var _this2 = this;
var _props$store$getState = this.props.store.getState().todoList.toJS();
var todos = _props$store$getState.todos;
var visibilityFilter = _props$store$getState.visibilityFilter;
var filteredTodos = todos.filter(function (t) {
if (visibilityFilter === 'SHOW_ACTIVE') return !t.completed;
if (visibilityFilter === 'SHOW_COMPLETED') return t.completed;
return true;
});
return React.createElement(
'div',
{ className: 'TodoApp' },
React.createElement(
'div',
{ className: 'TodoApp__filters' },
React.createElement(VisibilityFilterButton, {
filter: 'SHOW_ALL',
label: 'All',
active: function active(f) {
return visibilityFilter === f;
},
setFilter: function setFilter(f) {
return _this2.setFilter(f);
} }),
React.createElement(VisibilityFilterButton, {
filter: 'SHOW_ACTIVE',
label: 'Active',
active: function active(f) {
return visibilityFilter === f;
},
setFilter: function setFilter(f) {
return _this2.setFilter(f);
} }),
React.createElement(VisibilityFilterButton, {
filter: 'SHOW_COMPLETED',
label: 'Complete',
active: function active(f) {
return visibilityFilter === f;
},
setFilter: function setFilter(f) {
return _this2.setFilter(f);
} })
),
React.createElement(
'div',
{ className: 'TodoApp__form' },
React.createElement('input', {
className: 'TodoApp__form__input',
ref: function ref(node) {
return _this2.input = node;
},
placeholder: 'Add a todo...',
onKeyUp: function onKeyUp(e) {
return _this2.handleKeyUp(e);
} })
),
React.createElement(
'div',
{ className: 'TodoApp__todo-list' },
filteredTodos.map(function (todo) {
return React.createElement(Todo, {
todo: todo,
editing: _this2.state.editing === todo.id,
todoEditInputRef: function todoEditInputRef(node) {
return _this2.todoEditInput = node;
},
toggleTodo: function toggleTodo(id) {
return _this2.toggleTodo(id);
},
beginEditing: function beginEditing(id) {
return _this2.beginEditing(id);
},
endEditing: function endEditing(id) {
return _this2.endEditing(id);
},
deleteTodo: function deleteTodo(id) {
return _this2.deleteTodo(id);
} });
})
)
);
};
TodoApp.prototype.addTodo = function addTodo() {
if (!this.input.value) return;
this.props.store.dispatch({
type: 'ADD_TODO',
text: this.input.value
});
this.input.value = '';
};
TodoApp.prototype.toggleTodo = function toggleTodo(id) {
this.props.store.dispatch({
type: 'TOGGLE_TODO',
id: id
});
};
TodoApp.prototype.deleteTodo = function deleteTodo(id) {
this.props.store.dispatch({
type: 'DELETE_TODO',
id: id
});
};
TodoApp.prototype.setFilter = function setFilter(filter) {
this.props.store.dispatch({
type: 'SET_FILTER',
filter: filter
});
};
TodoApp.prototype.handleKeyUp = function handleKeyUp(e) {
if (e.keyCode === 13) this.addTodo();
};
TodoApp.prototype.beginEditing = function beginEditing(id) {
var _this3 = this;
this.setState({ editing: id }, function () {
var node = _this3.todoEditInput;
node.focus();
node.setSelectionRange(node.value.length, node.value.length);
});
};
TodoApp.prototype.endEditing = function endEditing(id) {
this.setState({ editing: null });
if (!this.todoEditInput.value) return;
this.props.store.dispatch({
type: 'EDIT_TODO',
text: this.todoEditInput.value,
id: id
});
};
return TodoApp;
}(Component);
/**
*
* INIT APP
*
*/
var todoApp = combineReducers({ todoList: todoList });
var store = createStore(todoApp);
var render = function render() {
ReactDOM.render(React.createElement(TodoApp, { store: store }), document.getElementById('root'));
};
render();
store.subscribe(render);
/**
*
* HELPERS
*
*/
function getTodoId(todos) {
var _Math;
return todos.size ? (_Math = Math).max.apply(_Math, todos.map(function (t) {
return t.get('id');
}).toJS()) + 1 : 0;
}
//# sourceURL=pen.js
</script>
</body></html>