Skip to content
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
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = function(grunt) {

var options = {
port: 8080
port: 8888
};

grunt.initConfig({
Expand Down
20 changes: 12 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>ReactJS + RefluxJS • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-app-css/index.css">
<link rel="stylesheet" href="app/bower_components/todomvc-app-css/index.css">
<!-- CSS overrides - remove if you don't need it -->
<link rel="stylesheet" href="css/app.css">
</head>
Expand All @@ -22,13 +22,17 @@
</footer>
<!-- Scripts here. Don't remove this ↓ -->
<!-- <script src="bower_components/todomvc-common/base.js"></script> -->
<script src="bower_components/react/react-with-addons.js"></script>
<script src="bower_components/react-router/dist/react-router.js"></script>
<script src="bower_components/react/JSXTransformer.js"></script>
<script src="bower_components/lodash/lodash.js"></script>
<script src="bower_components/reflux/dist/reflux.js"></script>
<script src="app/bower_components/react/react-with-addons.js"></script>
<script src="app/bower_components/react-router/dist/react-router.js"></script>
<script src="app/bower_components/react/JSXTransformer.js"></script>
<script src="app/bower_components/lodash/lodash.js"></script>
<script src="app/bower_components/reflux/dist/reflux.js"></script>
<script src="js/actions.js"></script>
<script src="js/store.js"></script>
<script type="text/jsx" src="js/components.jsx.js"></script>
</body>
<script type="text/jsx" src="js/TodoHeader.jsx.js"></script>
<script type="text/jsx" src="js/TodoFooter.jsx.js"></script>
<script type="text/jsx" src="js/TodoItem.jsx.js"></script>
<script type="text/jsx" src="js/TodoList.jsx.js"></script>
<script type="text/jsx" src="js/TodoApp.jsx.js"></script></body>
<script type="text/jsx" src="js/router.js"></script>
</html>
19 changes: 19 additions & 0 deletions js/TodoApp.jsx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
(function(React, ReactRouter, Reflux, TodoHeader, TodoFooter, todoListStore, global) {
// Renders the full application
// RouteHandler will always be TodoList, but with different 'showing' prop (all/completed/active)
global.TodoApp = React.createClass({
// this will cause setState({list:updatedlist}) whenever the store does trigger(updatedlist)
mixins: [Reflux.connect(todoListStore,"list")],

render: function() {
return (
<div>
<TodoHeader />
<ReactRouter.RouteHandler list={this.state.list} />
<TodoFooter list={this.state.list} />
</div>
);
}
});

})(window.React, window.ReactRouter, window.Reflux,window.TodoHeader, window.TodoFooter, window.todoListStore, window);
35 changes: 35 additions & 0 deletions js/TodoFooter.jsx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
(function(React, ReactRouter, TodoActions, global) {
// Renders the bottom item count, navigation bar and clearallcompleted button
// Used in TodoApp
global.TodoFooter = React.createClass({
propTypes: {
list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
},
render: function() {
var nbrcompleted = _.filter(this.props.list, "isComplete").length,
nbrtotal = this.props.list.length,
nbrincomplete = nbrtotal-nbrcompleted,
clearButtonClass = React.addons.classSet({hidden: nbrcompleted < 1}),
footerClass = React.addons.classSet({hidden: !nbrtotal }),
completedLabel = "Clear completed (" + nbrcompleted + ")",
itemsLeftLabel = nbrincomplete === 1 ? " item left" : " items left";
return (
<footer id="footer" className={footerClass}>
<span id="todo-count"><strong>{nbrincomplete}</strong>{itemsLeftLabel}</span>
<ul id="filters">
<li>
<ReactRouter.Link activeClassName="selected" to="All">All</ReactRouter.Link>
</li>
<li>
<ReactRouter.Link activeClassName="selected" to="Active">Active</ReactRouter.Link>
</li>
<li>
<ReactRouter.Link activeClassName="selected" to="Completed">Completed</ReactRouter.Link>
</li>
</ul>
<button id="clear-completed" className={clearButtonClass} onClick={TodoActions.clearCompleted}>{completedLabel}</button>
</footer>
);
}
});
})(window.React, window.ReactRouter, window.TodoActions, window);
24 changes: 24 additions & 0 deletions js/TodoHeader.jsx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(function(React, ReactRouter, Reflux, TodoActions, todoListStore, global) {
// Renders the headline and the form for creating new todos.
// Used in TodoApp
// Observe that the toogleall button is NOT rendered here, but in TodoMain (it is then moved up to the header with CSS)
global.TodoHeader = React.createClass({
handleValueChange: function(evt) {
var text = evt.target.value;
if (evt.which === 13 && text) { // hit enter, create new item if field isn't empty
TodoActions.addItem(text);
evt.target.value = '';
} else if (evt.which === 27) { // hit escape, clear without creating
evt.target.value = '';
}
},
render: function() {
return (
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autoFocus onKeyUp={this.handleValueChange}/>
</header>
);
}
});
})(window.React, window.ReactRouter, window.Reflux, window.TodoActions, window.todoListStore, window);
79 changes: 79 additions & 0 deletions js/TodoItem.jsx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
(function(React, TodoActions, global) {
// Renders a single Todo item in the list
// Used in TodoMain
global.TodoItem = React.createClass({
propTypes: {
label: React.PropTypes.string.isRequired,
isComplete: React.PropTypes.bool.isRequired,
id: React.PropTypes.number
},
mixins: [React.addons.LinkedStateMixin], // exposes this.linkState used in render
getInitialState: function() {
return {};
},
handleToggle: function(evt) {
TodoActions.toggleItem(this.props.id);
},
handleEditStart: function(evt) {
evt.preventDefault();
// because of linkState call in render, field will get value from this.state.editValue
this.setState({
isEditing: true,
editValue: this.props.label
}, function() {
this.refs.editInput.getDOMNode().focus();
});
},
handleCompleted: function(evt){
evt.preventDefault();
// because of linkState call in render, field will get value from this.state.editValue
setTimeout(function () {
if(!this.state.isEditing){
this.refs.toggleCompleted.getDOMNode().click();
}
}.bind(this), 200);
},
handleValueChange: function(evt) {
var text = this.state.editValue; // because of the linkState call in render, this is the contents of the field
// we pressed enter, if text isn't empty we blur the field which will cause a save
if (evt.which === 13 && text) {
this.refs.editInput.getDOMNode().blur();
}
// pressed escape. set editing to false before blurring so we won't save
else if (evt.which === 27) {
this.setState({ isEditing: false },function(){
this.refs.editInput.getDOMNode().blur();
});
}
},
handleBlur: function() {
var text = this.state.editValue; // because of the linkState call in render, this is the contents of the field
// unless we're not editing (escape was pressed) or text is empty, save!
if (this.state.isEditing && text) {
TodoActions.editItem(this.props.id, text);
}
// whatever the outcome, if we left the field we're not editing anymore
this.setState({isEditing:false});
},
handleDestroy: function() {
TodoActions.removeItem(this.props.id);
},
render: function() {
var classes = React.addons.classSet({
'completed': this.props.isComplete,
'editing': this.state.isEditing
});
return (
<li className={classes}>
<div className="view">
<input ref="toggleCompleted" className="toggle" type="checkbox" checked={!!this.props.isComplete} onChange={this.handleToggle} />
<label onDoubleClick={this.handleEditStart} onClick={this.handleCompleted}>{this.props.label}</label>
<button className="destroy" onClick={this.handleDestroy}></button>
</div>
<input ref="editInput" className="edit" valueLink={this.linkState('editValue')} onKeyUp={this.handleValueChange} onBlur={this.handleBlur} />
</li>
);
}
});
})(window.React, window.TodoActions, window);

44 changes: 44 additions & 0 deletions js/TodoList.jsx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/** @jsx React.DOM */

(function(React, ReactRouter, TodoItem, TodoActions, global) {

// Renders the todo list as well as the toggle all button
// Used in TodoApp
global.TodoList = React.createClass({
mixins: [ ReactRouter.State ],
propTypes: {
list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
},
toggleAll: function(evt) {
TodoActions.toggleAllItems(evt.target.checked);
},
render: function() {
var filteredList;
switch(this.getPath()){
case '/completed':
filteredList = _.filter(this.props.list,function(item){ return item.isComplete; });
break;
case '/active':
filteredList = _.filter(this.props.list,function(item){ return !item.isComplete; });
break;
default:
filteredList = this.props.list;
}
var classes = React.addons.classSet({
"hidden": this.props.list.length < 1
});
return (
<section id="main" className={classes}>
<input id="toggle-all" type="checkbox" onChange={this.toggleAll} />
<label htmlFor="toggle-all">Mark all as complete</label>
<ul id="todo-list">
{ filteredList.map(function(item){
return <TodoItem label={item.label} isComplete={item.isComplete} id={item.key} key={item.key}/>;
})}
</ul>
</section>
);
}
});

})(window.React, window.ReactRouter, window.TodoItem, window.TodoActions, window);
Loading