diff --git a/README.md b/README.md
index 13b9ab4..1f07f5f 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# learn-reactjs-in-steps
+**These videos were created in 2015 and now they are out dated.**
+
This is code repository for videos created as part of [Learn ReactJS in steps](http://bigbinary.com/videos/learn-reactjs-in-steps).
In this application we start with a "Hello World" applillcation and we add features to the application to make a fully functional "TODO application".
diff --git a/ep19-call-api-jquery/README.md b/ep19-call-api-jquery/README.md
index 0f6225e..2af3db9 100644
--- a/ep19-call-api-jquery/README.md
+++ b/ep19-call-api-jquery/README.md
@@ -9,5 +9,4 @@ Visit http://localhost:8080 in browser.
#### Notes
-* `npm install random-key --save`
-* [Source code](...)
+* `npm install jquery --save`
diff --git a/ep20-using-fetch/README.md b/ep20-using-fetch/README.md
index 0f6225e..a404b18 100644
--- a/ep20-using-fetch/README.md
+++ b/ep20-using-fetch/README.md
@@ -9,5 +9,4 @@ Visit http://localhost:8080 in browser.
#### Notes
-* `npm install random-key --save`
-* [Source code](...)
+* `npm install fetch --save`
diff --git a/ep21-general-refactoring/README.md b/ep21-general-refactoring/README.md
index 0f6225e..2712807 100644
--- a/ep21-general-refactoring/README.md
+++ b/ep21-general-refactoring/README.md
@@ -6,8 +6,3 @@ npm start
```
Visit http://localhost:8080 in browser.
-
-#### Notes
-
-* `npm install random-key --save`
-* [Source code](...)
diff --git a/ep22-building-todoactions/README.md b/ep22-building-todoactions/README.md
index 0f6225e..2712807 100644
--- a/ep22-building-todoactions/README.md
+++ b/ep22-building-todoactions/README.md
@@ -6,8 +6,3 @@ npm start
```
Visit http://localhost:8080 in browser.
-
-#### Notes
-
-* `npm install random-key --save`
-* [Source code](...)
diff --git a/ep23-building-todostore/README.md b/ep23-building-todostore/README.md
index 0f6225e..2712807 100644
--- a/ep23-building-todostore/README.md
+++ b/ep23-building-todostore/README.md
@@ -6,8 +6,3 @@ npm start
```
Visit http://localhost:8080 in browser.
-
-#### Notes
-
-* `npm install random-key --save`
-* [Source code](...)
diff --git a/ep24-delete-todo-using-flux/README.md b/ep24-delete-todo-using-flux/README.md
index 0f6225e..2712807 100644
--- a/ep24-delete-todo-using-flux/README.md
+++ b/ep24-delete-todo-using-flux/README.md
@@ -6,8 +6,3 @@ npm start
```
Visit http://localhost:8080 in browser.
-
-#### Notes
-
-* `npm install random-key --save`
-* [Source code](...)
diff --git a/ep25-add-todo-using-flux/README.md b/ep25-add-todo-using-flux/README.md
index 0f6225e..2712807 100644
--- a/ep25-add-todo-using-flux/README.md
+++ b/ep25-add-todo-using-flux/README.md
@@ -6,8 +6,3 @@ npm start
```
Visit http://localhost:8080 in browser.
-
-#### Notes
-
-* `npm install random-key --save`
-* [Source code](...)
diff --git a/ep26-enhance-todo-store/README.md b/ep26-enhance-todo-store/README.md
index 0f6225e..2712807 100644
--- a/ep26-enhance-todo-store/README.md
+++ b/ep26-enhance-todo-store/README.md
@@ -6,8 +6,3 @@ npm start
```
Visit http://localhost:8080 in browser.
-
-#### Notes
-
-* `npm install random-key --save`
-* [Source code](...)
diff --git a/ep27-use-dispatcher/README.md b/ep27-use-dispatcher/README.md
index 0f6225e..2712807 100644
--- a/ep27-use-dispatcher/README.md
+++ b/ep27-use-dispatcher/README.md
@@ -6,8 +6,3 @@ npm start
```
Visit http://localhost:8080 in browser.
-
-#### Notes
-
-* `npm install random-key --save`
-* [Source code](...)
diff --git a/ep28-more-dispatcher-usage/README.md b/ep28-more-dispatcher-usage/README.md
index 0f6225e..2712807 100644
--- a/ep28-more-dispatcher-usage/README.md
+++ b/ep28-more-dispatcher-usage/README.md
@@ -6,8 +6,3 @@ npm start
```
Visit http://localhost:8080 in browser.
-
-#### Notes
-
-* `npm install random-key --save`
-* [Source code](...)
diff --git a/ep28-more-dispatcher-usage/app/actions/TodoActions.js b/ep28-more-dispatcher-usage/app/actions/TodoActions.js
index 9da58ed..b6f2666 100644
--- a/ep28-more-dispatcher-usage/app/actions/TodoActions.js
+++ b/ep28-more-dispatcher-usage/app/actions/TodoActions.js
@@ -1,37 +1,32 @@
var api = require("../utils/api");
var TodoStore = require("../stores/TodoStore");
var AppDispatcher = require('../dispatcher/AppDispatcher');
+var Constants = require("../utils/constants");
var TodoActions = {
addTodo: (todo) => {
- console.log("adding TODO");
api.addTodo(todo)
.then( () => {
- console.log("Added TODO successfully");
TodoActions.getAllTodosAndUpdateStore();
})
},
deleteTodo: (todo) => {
- console.log("Deleting TODO");
api.deleteTodo(todo.id)
.then( () => {
- console.log("Deleted TODO successfully");
AppDispatcher.dispatch({
- actionType: 'TODO_DELETE',
+ actionType: Constants.TODO_DELETE,
todo: todo
});
})
},
markTodoDone: (todo) => {
- console.log("Marking TODO as done");
api.markTodoDone(todo)
.then( () => {
- console.log("marked TODO as done successfully");
AppDispatcher.dispatch({
- actionType: 'TODO_DONE',
+ actionType: Constants.TODO_DONE,
todo: todo
});
@@ -39,26 +34,22 @@ var TodoActions = {
},
markTodoUnDone: (todo) => {
- console.log("Marking TODO as undone");
api.markTodoUnDone(todo)
.then( () => {
- console.log("marked TODO as undone successfully");
AppDispatcher.dispatch({
- actionType: 'TODO_UNDONE',
+ actionType: Constants.TODO_UNDONE,
todo: todo
});
})
},
getAllTodosAndUpdateStore: () => {
- console.log("Performing getAllTodos");
api.getTodos()
.then( (responseData) => {
var todos = responseData.todos;
- console.log("new todos", todos);
TodoStore.setTodos(todos);
AppDispatcher.dispatch({
- actionType: 'TODO_ADD'
+ actionType: Constants.TODO_ADD
});
})
}
diff --git a/ep28-more-dispatcher-usage/app/components/App.jsx b/ep28-more-dispatcher-usage/app/components/App.jsx
index 1dff5bb..b9150d2 100644
--- a/ep28-more-dispatcher-usage/app/components/App.jsx
+++ b/ep28-more-dispatcher-usage/app/components/App.jsx
@@ -11,7 +11,6 @@ export default class App extends React.Component {
constructor () {
super();
this.state = { title: '', todos: [] };
-
this.getAllTodos();
}
diff --git a/ep28-more-dispatcher-usage/app/components/DisplayList.jsx b/ep28-more-dispatcher-usage/app/components/DisplayList.jsx
index 3cd852e..a254b58 100644
--- a/ep28-more-dispatcher-usage/app/components/DisplayList.jsx
+++ b/ep28-more-dispatcher-usage/app/components/DisplayList.jsx
@@ -10,7 +10,8 @@ export default class DisplayList extends React.Component {
- }) }
+ })
+ }
}
diff --git a/ep28-more-dispatcher-usage/app/stores/TodoStore.js b/ep28-more-dispatcher-usage/app/stores/TodoStore.js
index 0db76d5..f2b31f4 100644
--- a/ep28-more-dispatcher-usage/app/stores/TodoStore.js
+++ b/ep28-more-dispatcher-usage/app/stores/TodoStore.js
@@ -1,25 +1,22 @@
var AppDispatcher = require('../dispatcher/AppDispatcher');
+var Constants = require("../utils/constants");
AppDispatcher.register(function(action) {
switch(action.actionType) {
- case 'TODO_DONE':
- console.log("Handling TODO_DONE using dispatcher in store");
+ case Constants.TODO_DONE:
TodoStore.markTodoDone(action.todo);
break;
- case 'TODO_UNDONE':
- console.log("Handling TODO_UNDONE using dispatcher in store");
+ case Constants.TODO_UNDONE:
TodoStore.markTodoUnDone(action.todo);
break;
- case 'TODO_DELETE':
- console.log("Handling TODO_DELETE using dispatcher in store");
+ case Constants.TODO_DELETE:
TodoStore.deleteTodo(action.todo);
break;
- case 'TODO_ADD':
- console.log("Handling TODO_ADD using dispatcher in store");
+ case Constants.TODO_ADD:
TodoStore.getTodos();
break;
}
diff --git a/ep28-more-dispatcher-usage/app/utils/constants.js b/ep28-more-dispatcher-usage/app/utils/constants.js
index 2affc3c..3025eba 100644
--- a/ep28-more-dispatcher-usage/app/utils/constants.js
+++ b/ep28-more-dispatcher-usage/app/utils/constants.js
@@ -1,5 +1,10 @@
var Constants = {
BASE_URL: "http://lrjis-api-production.herokuapp.com/api/v1/",
- API_KEY: "4d5e466a-97a4-46ba-bb3d-3da6c4347965"
+ API_KEY: "4d5e466a-97a4-46ba-bb3d-3da6c4347965",
+ TODO_DONE: 'TODO_DONE',
+ TODO_UNDONE: 'TODO_UNDONE',
+ TODO_DELETE: 'TODO_DELETE',
+ TODO_ADD: 'TODO_ADD'
};
+
module.exports = Constants;
diff --git a/ep29-emit/README.md b/ep29-emit/README.md
new file mode 100644
index 0000000..cda0d27
--- /dev/null
+++ b/ep29-emit/README.md
@@ -0,0 +1,14 @@
+#### Setting up the application
+
+```
+npm install
+npm start
+```
+
+Visit http://localhost:8080 in browser.
+
+#### Notes
+
+* [Nodejs Events](https://nodejs.org/api/events.html)
+* [object-assign](https://www.npmjs.com/package/object-assign)
+* `npm install --save object-assign`
diff --git a/ep29-emit/app/actions/TodoActions.js b/ep29-emit/app/actions/TodoActions.js
new file mode 100644
index 0000000..af8571b
--- /dev/null
+++ b/ep29-emit/app/actions/TodoActions.js
@@ -0,0 +1,68 @@
+var api = require("../utils/api");
+var TodoStore = require("../stores/TodoStore");
+var AppDispatcher = require('../dispatcher/AppDispatcher');
+
+var TodoActions = {
+
+ addTodo: (todo) => {
+ console.log("adding TODO");
+ api.addTodo(todo)
+ .then( () => {
+ api.getTodos()
+ .then( (responseData) => {
+ var todos = responseData.todos;
+ console.log("All todos", todos);
+ TodoStore.setTodos(todos);
+ })
+ })
+ .then( () => {
+ console.log("Added TODO successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_ADD'
+ });
+ })
+ },
+
+ deleteTodo: (todo) => {
+ console.log("Deleting TODO");
+ api.deleteTodo(todo.id)
+ .then( () => {
+ console.log("Deleted TODO successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_DELETE',
+ todo: todo
+ });
+ })
+ },
+
+ markTodoDone: (todo) => {
+ console.log("Marking TODO as done");
+ api.markTodoDone(todo)
+ .then( () => {
+ console.log("marked TODO as done successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_DONE',
+ todo: todo
+ });
+
+ })
+ },
+
+ markTodoUnDone: (todo) => {
+ console.log("Marking TODO as undone");
+ api.markTodoUnDone(todo)
+ .then( () => {
+ console.log("marked TODO as undone successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_UNDONE',
+ todo: todo
+ });
+ })
+ },
+
+
+
+
+}
+
+module.exports = TodoActions;
diff --git a/ep29-emit/app/components/App.jsx b/ep29-emit/app/components/App.jsx
new file mode 100644
index 0000000..370ee75
--- /dev/null
+++ b/ep29-emit/app/components/App.jsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import DisplayList from './DisplayList';
+
+var rand = require('random-key');
+var api = require("../utils/api");
+var TodoActions = require("../actions/TodoActions");
+var TodoStore = require("../stores/TodoStore");
+
+export default class App extends React.Component {
+
+ constructor () {
+ super();
+ this.state = { title: '', todos: [] };
+
+ this.getAllTodos();
+ }
+
+ componentDidMount () {
+ var storeIsTellingUsThatDataHasChanged = () => {
+ console.log("Store is telling us that data has change");
+ var todos = TodoStore.getTodos();
+ console.log("todos is");
+ console.log(todos);
+ this.setState({todos: todos});
+ }
+ TodoStore.addChangeListener(storeIsTellingUsThatDataHasChanged);
+ }
+
+ getAllTodos () {
+ api.getTodos()
+ .then( (responseData) => {
+ var todos = responseData.todos;
+ this.setState({todos: todos });
+ TodoStore.setTodos(todos);
+ })
+ }
+
+ handleSubmit (event) {
+ event.preventDefault();
+
+ var newTodo = { title: this.state.title, done: false };
+
+ TodoActions.addTodo(newTodo);
+ this.setState({ title: '' });
+ }
+
+ handleChange (event) {
+ var title = event.target.value;
+ this.setState({ title: title });
+ }
+
+ handleClearCompleted (event) {
+ var newTodos = this.state.todos.filter((todo) => { return !todo.done});
+ this.setState({ todos: newTodos });
+ }
+
+ render () {
+ return
+
TODO
+
+
+
+
+
+
;
+ }
+}
diff --git a/ep29-emit/app/components/DisplayItem.jsx b/ep29-emit/app/components/DisplayItem.jsx
new file mode 100644
index 0000000..47d9af2
--- /dev/null
+++ b/ep29-emit/app/components/DisplayItem.jsx
@@ -0,0 +1,86 @@
+import React from 'react';
+
+var TodoActions = require('../actions/TodoActions');
+
+export default class DisplayItem extends React.Component {
+
+ constructor () {
+ super();
+ this.state = { editing: false }
+ }
+
+ componentDidMount () {
+ this.setState({ changedText: this.props.todo.title });
+ }
+
+ handleEditing (event) {
+ this.setState({ editing: true, changedText: this.props.todo.title });
+ }
+
+ handleEditingDone (event) {
+ if (event.keyCode === 13 ) { // submit
+ this.setState({ editing: false });
+ }
+ }
+
+ handleEditingChange (event) {
+ var _changedText = event.target.value;
+ this.setState({ changedText: _changedText });
+ }
+
+ toggleDone (todo) {
+ if (todo.done) {
+ TodoActions.markTodoUnDone(todo);
+ } else {
+ TodoActions.markTodoDone(todo);
+ }
+ }
+
+ handleDeleteTodoClick (todo) {
+ TodoActions.deleteTodo(todo);
+ }
+
+ render () {
+ var todo = this.props.todo;
+
+ var viewStyle = {};
+ var editStyle = {};
+
+ if (this.state.editing) {
+ viewStyle.display = 'none';
+ } else {
+ editStyle.display = 'none';
+ }
+
+ return
+
+
+
+
+
+
+ [x]
+
+
+
+
+
+ }
+
+}
+
+DisplayItem.propTypes = {
+ todo: React.PropTypes.object.isRequired
+}
diff --git a/ep29-emit/app/components/DisplayList.jsx b/ep29-emit/app/components/DisplayList.jsx
new file mode 100644
index 0000000..3cd852e
--- /dev/null
+++ b/ep29-emit/app/components/DisplayList.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import DisplayItem from './DisplayItem';
+
+export default class DisplayList extends React.Component {
+
+ render () {
+ return
+ { this.props.todos.map((todo, i) => {
+ return
+ }) }
+
+ }
+
+}
+
+DisplayList.propTypes = {
+ todos: React.PropTypes.array.isRequired
+}
diff --git a/ep29-emit/app/dispatcher/AppDispatcher.js b/ep29-emit/app/dispatcher/AppDispatcher.js
new file mode 100644
index 0000000..5231a4e
--- /dev/null
+++ b/ep29-emit/app/dispatcher/AppDispatcher.js
@@ -0,0 +1,3 @@
+var Dispatcher = require('flux').Dispatcher;
+
+module.exports = new Dispatcher();
diff --git a/ep29-emit/app/main.jsx b/ep29-emit/app/main.jsx
new file mode 100644
index 0000000..aee7cdf
--- /dev/null
+++ b/ep29-emit/app/main.jsx
@@ -0,0 +1,14 @@
+import './stylesheets/main.css';
+
+import React from 'react';
+import App from './components/App';
+
+main();
+
+function main() {
+ var div = document.createElement('div');
+ div.setAttribute("id", "todoapp");
+ document.body.appendChild(div);
+
+ React.render(, div);
+}
diff --git a/ep29-emit/app/stores/TodoStore.js b/ep29-emit/app/stores/TodoStore.js
new file mode 100644
index 0000000..9c5ed9e
--- /dev/null
+++ b/ep29-emit/app/stores/TodoStore.js
@@ -0,0 +1,81 @@
+var AppDispatcher = require('../dispatcher/AppDispatcher');
+var EventEmitter = require('events').EventEmitter;
+var assign = require('object-assign');
+
+AppDispatcher.register(function(action) {
+
+ switch(action.actionType) {
+ case 'TODO_DONE':
+ console.log("Handling TODO_DONE using dispatcher in store");
+ TodoStore.markTodoDone(action.todo);
+ break;
+
+ case 'TODO_UNDONE':
+ console.log("Handling TODO_UNDONE using dispatcher in store");
+ TodoStore.markTodoUnDone(action.todo);
+ break;
+
+ case 'TODO_DELETE':
+ console.log("Handling TODO_DELETE using dispatcher in store");
+ TodoStore.deleteTodo(action.todo);
+ break;
+
+ case 'TODO_ADD':
+ console.log("Handling TODO_ADD using dispatcher in store");
+ TodoStore.getTodos();
+ break;
+ }
+
+});
+
+var _todos = {};
+var CHANGE_EVENT = 'change';
+
+var TodoStore = assign({}, EventEmitter.prototype, {
+
+ deleteTodo: (todo) => {
+ var newTodos = _todos.filter( (t) => {
+ return t.id != todo.id
+ } )
+ _todos = newTodos;
+ TodoStore.emitChange();
+ },
+
+ markTodoDone: (todo) => {
+ var _todo = _todos.filter((t) => {
+ return t.id === todo.id;
+ })[0];
+
+ _todo.done = true;
+ TodoStore.emitChange();
+ },
+
+ markTodoUnDone: (todo) => {
+ var _todo = _todos.filter((t) => {
+ return t.id === todo.id;
+ })[0];
+
+ _todo.done = false;
+ TodoStore.emitChange();
+ },
+
+ setTodos: (todos) => {
+ _todos = todos;
+ TodoStore.emitChange();
+ },
+
+ getTodos: () => {
+ return _todos;
+ },
+
+ emitChange: function() {
+ this.emit(CHANGE_EVENT);
+ },
+
+ addChangeListener: function (callback) {
+ console.log("registering callback for changelistener");
+ this.on(CHANGE_EVENT, callback);
+ }
+})
+
+module.exports = TodoStore;
diff --git a/ep29-emit/app/stylesheets/main.css b/ep29-emit/app/stylesheets/main.css
new file mode 100644
index 0000000..ac2d81f
--- /dev/null
+++ b/ep29-emit/app/stylesheets/main.css
@@ -0,0 +1,190 @@
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
+ line-height: 1.4em;
+ background: #eeeeee;
+ color: #333333;
+ width: 520px;
+ margin: 0 auto;
+ -webkit-font-smoothing: antialiased;
+}
+
+#todoapp {
+ background: #fff;
+ padding: 20px;
+ margin-bottom: 40px;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -webkit-border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -ms-border-radius: 0 0 5px 5px;
+ -o-border-radius: 0 0 5px 5px;
+ border-radius: 0 0 5px 5px;
+}
+
+#todoapp h1 {
+ font-size: 36px;
+ font-weight: bold;
+ text-align: center;
+ padding: 0 0 10px 0;
+}
+
+#todoapp input[type="text"] {
+ width: 466px;
+ font-size: 24px;
+ font-family: inherit;
+ line-height: 1.4em;
+ border: 0;
+ outline: none;
+ padding: 6px;
+ border: 1px solid #999999;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+}
+
+#todoapp input::-webkit-input-placeholder {
+ font-style: italic;
+}
+
+#todo-list {
+ margin: 10px 0;
+ padding: 0;
+ list-style: none;
+}
+
+#todo-list li {
+ padding: 18px 20px 18px 0;
+ position: relative;
+ font-size: 24px;
+ border-bottom: 1px solid #cccccc;
+}
+
+#todo-list li:last-child {
+ border-bottom: none;
+}
+
+#todo-list li.done label {
+ color: #777777;
+ text-decoration: line-through;
+}
+
+#todo-list .destroy {
+ position: absolute;
+ right: 5px;
+ top: 20px;
+ display: none;
+ cursor: pointer;
+ width: 20px;
+ height: 20px;
+}
+
+#todo-list li:hover .destroy {
+ display: block;
+}
+
+#todo-list .destroy:hover {
+ background-position: 0 -20px;
+}
+
+#todo-list li.editing {
+ border-bottom: none;
+ margin-top: -1px;
+ padding: 0;
+}
+
+#todo-list li.editing:last-child {
+ margin-bottom: -1px;
+}
+
+#todo-list li .view label {
+ word-break: break-word;
+}
+
+#todoapp footer {
+ margin: 0 -20px -20px -20px;
+ overflow: hidden;
+ color: #555555;
+ background: #f4fce8;
+ border-top: 1px solid #ededed;
+ padding: 0 20px;
+ line-height: 37px;
+ -webkit-border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -ms-border-radius: 0 0 5px 5px;
+ -o-border-radius: 0 0 5px 5px;
+ border-radius: 0 0 5px 5px;
+}
+
+#clear-completed {
+ float: right;
+ line-height: 20px;
+ text-decoration: none;
+ background: rgba(0, 0, 0, 0.1);
+ color: #555555;
+ font-size: 11px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+ padding: 0 10px 1px;
+ cursor: pointer;
+ -webkit-border-radius: 12px;
+ -moz-border-radius: 12px;
+ -ms-border-radius: 12px;
+ -o-border-radius: 12px;
+ border-radius: 12px;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+}
+
+#clear-completed:hover {
+ background: rgba(0, 0, 0, 0.15);
+ -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -ms-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+}
+
+#clear-completed:active {
+ position: relative;
+ top: 1px;
+}
+
+#todo-count span {
+ font-weight: bold;
+}
+
+#instructions {
+ margin: 10px auto;
+ color: #777777;
+ text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
+ text-align: center;
+}
+
+#instructions a {
+ color: #336699;
+}
+
+#credits {
+ margin: 30px auto;
+ color: #999;
+ text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
+ text-align: center;
+}
+
+#credits a {
+ color: #888;
+}
diff --git a/ep29-emit/app/utils/api.js b/ep29-emit/app/utils/api.js
new file mode 100644
index 0000000..1cfd44b
--- /dev/null
+++ b/ep29-emit/app/utils/api.js
@@ -0,0 +1,62 @@
+require('whatwg-fetch');
+var Constants = require("./constants");
+var HEADER = { 'Accept': 'application/json', 'Content-Type': 'application/json' }
+
+var api = {
+ generateUrlWithApiKey(endpoint) {
+ return Constants.BASE_URL + endpoint + '?api_key=' + Constants.API_KEY;
+ },
+
+ getTodos () {
+ var url = this.generateUrlWithApiKey('todos');
+ return fetch(url)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to get all tasks list.', error) );
+ },
+
+ addTodo (todo) {
+ var url = this.generateUrlWithApiKey('todos');
+ var options = {
+ method: 'POST',
+ headers: HEADER,
+ body: JSON.stringify({todo: todo})
+ };
+ return fetch(url, options)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to add a TODO.', error) );
+ },
+
+ markTodoDone (todo) {
+ var url = this.generateUrlWithApiKey('todos/' + todo.id);
+ var options = {
+ method: 'PUT',
+ headers: HEADER,
+ body: JSON.stringify({done: true})
+ };
+ return fetch(url, options)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to mark task as done. ', error) );
+ },
+
+ markTodoUnDone (todo) {
+ var url = this.generateUrlWithApiKey('todos/' + todo.id);
+ var options = {
+ method: 'PUT',
+ headers: HEADER,
+ body: JSON.stringify({done: false})
+ };
+ return fetch(url, options)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to mark task as undone. ', error) );
+ },
+
+ deleteTodo (idToBeDeleted, processDataCallback) {
+ var url = this.generateUrlWithApiKey('todos/' + idToBeDeleted);
+ return fetch(url, { method: 'DELETE' })
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to delete TODO.', error) );
+ },
+
+};
+
+module.exports = api;
diff --git a/ep29-emit/app/utils/constants.js b/ep29-emit/app/utils/constants.js
new file mode 100644
index 0000000..2affc3c
--- /dev/null
+++ b/ep29-emit/app/utils/constants.js
@@ -0,0 +1,5 @@
+var Constants = {
+ BASE_URL: "http://lrjis-api-production.herokuapp.com/api/v1/",
+ API_KEY: "4d5e466a-97a4-46ba-bb3d-3da6c4347965"
+};
+module.exports = Constants;
diff --git a/ep29-emit/package.json b/ep29-emit/package.json
new file mode 100644
index 0000000..3a4ee8f
--- /dev/null
+++ b/ep29-emit/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "learning-reactjs-in-steps",
+ "version": "0.0.1",
+ "description": "Learn ReactJS in steps",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "TAGET=build webpack",
+ "start": "TARGET=dev webpack-dev-server --devtool eval --progress --colors --hot"
+ },
+ "author": "Neeraj Singh",
+ "license": "ISC",
+ "devDependencies": {
+ "babel-core": "^5.6.15",
+ "babel-loader": "^5.3.1",
+ "css-loader": "^0.15.1",
+ "html-webpack-plugin": "^1.5.2",
+ "node-libs-browser": "^0.5.2",
+ "react-hot-loader": "^1.2.7",
+ "style-loader": "^0.12.3",
+ "webpack": "^1.10.1",
+ "webpack-dev-server": "^1.10.1",
+ "webpack-merge": "^0.1.2"
+ },
+ "dependencies": {
+ "flux": "^2.1.1",
+ "jquery": "1.11.3",
+ "object-assign": "^4.0.1",
+ "random-key": "^0.3.2",
+ "react": "^0.13.3",
+ "whatwg-fetch": "^0.9.0"
+ }
+}
diff --git a/ep29-emit/webpack.config.js b/ep29-emit/webpack.config.js
new file mode 100644
index 0000000..bb8e6c1
--- /dev/null
+++ b/ep29-emit/webpack.config.js
@@ -0,0 +1,54 @@
+var path = require('path');
+var HtmlWebpackPlugin = require('html-webpack-plugin');
+var merge = require('webpack-merge');
+
+var TARGET = process.env.TARGET;
+var ROOT_PATH = path.resolve(__dirname);
+
+var common = {
+ entry: [path.resolve(ROOT_PATH, 'app/main')],
+ resolve: {
+ extensions: ['', '.js', '.jsx'],
+ },
+ output: {
+ path: path.resolve(ROOT_PATH, 'build'),
+ filename: 'bundle.js',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({ title: 'Todo app', }),
+ ],
+ module: {
+ loaders: [
+ {
+ test: /\.css$/,
+ loaders: ['style', 'css'],
+ },
+ {
+ test: /\.jsx?$/,
+ loader: 'babel?stage=1',
+ include: path.resolve(ROOT_PATH, 'app'),
+ }
+ ],
+ },
+};
+
+if (TARGET === 'build') {
+ module.exports = common;
+}
+
+if (TARGET === 'dev') {
+ module.exports = merge(common, {
+ entry: [
+ 'webpack-dev-server/client?http://0.0.0.0:8080',
+ 'webpack/hot/dev-server'
+ ],
+ module: {
+ loaders: [ {
+ test: /\.jsx?$/,
+ loaders: ['react-hot', 'babel?stage=1'],
+ include: path.resolve(ROOT_PATH, 'app'),
+ },
+ ],
+ },
+ });
+}
diff --git a/ep30-extract-form-into-componenet-from-app/README.md b/ep30-extract-form-into-componenet-from-app/README.md
new file mode 100644
index 0000000..2712807
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/README.md
@@ -0,0 +1,8 @@
+#### Setting up the application
+
+```
+npm install
+npm start
+```
+
+Visit http://localhost:8080 in browser.
diff --git a/ep30-extract-form-into-componenet-from-app/app/actions/TodoActions.js b/ep30-extract-form-into-componenet-from-app/app/actions/TodoActions.js
new file mode 100644
index 0000000..bffb30d
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/actions/TodoActions.js
@@ -0,0 +1,71 @@
+var api = require("../utils/api");
+var TodoStore = require("../stores/TodoStore");
+var AppDispatcher = require('../dispatcher/AppDispatcher');
+
+var TodoActions = {
+
+ allTodos: () => {
+ api.getTodos()
+ .then( (responseData) => {
+ console.log("Got All TODOs successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_ALL',
+ todos: todos
+ });
+ })
+ },
+
+ addTodo: (todo) => {
+ console.log("adding TODO");
+ api.addTodo(todo)
+ .then( () => {
+ allTodos();
+ })
+ .then( () => {
+ console.log("Added TODO successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_ADD'
+ });
+ })
+ },
+
+ deleteTodo: (todo) => {
+ console.log("Deleting TODO");
+ api.deleteTodo(todo.id)
+ .then( () => {
+ console.log("Deleted TODO successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_DELETE',
+ todo: todo
+ });
+ })
+ },
+
+ markTodoDone: (todo) => {
+ console.log("Marking TODO as done");
+ api.markTodoDone(todo)
+ .then( () => {
+ console.log("marked TODO as done successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_DONE',
+ todo: todo
+ });
+
+ })
+ },
+
+ markTodoUnDone: (todo) => {
+ console.log("Marking TODO as undone");
+ api.markTodoUnDone(todo)
+ .then( () => {
+ console.log("marked TODO as undone successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_UNDONE',
+ todo: todo
+ });
+ })
+ },
+
+}
+
+module.exports = TodoActions;
diff --git a/ep30-extract-form-into-componenet-from-app/app/components/App.jsx b/ep30-extract-form-into-componenet-from-app/app/components/App.jsx
new file mode 100644
index 0000000..3f27795
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/components/App.jsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import TodoForm from './TodoForm';
+import DisplayList from './DisplayList';
+
+var rand = require('random-key');
+var api = require("../utils/api");
+var TodoActions = require("../actions/TodoActions");
+var TodoStore = require("../stores/TodoStore");
+
+export default class App extends React.Component {
+
+ constructor () {
+ super();
+ this.state = { title: '', todos: [] };
+
+ TodoActions.allTodos();
+ }
+
+ componentDidMount () {
+ var storeIsTellingUsThatDataHasChanged = () => {
+ console.log("Store is telling us that data has change");
+ var todos = TodoStore.getTodos();
+ console.log("todos :", todos);
+ this.setState({todos: todos});
+ }
+ TodoStore.addChangeListener(storeIsTellingUsThatDataHasChanged);
+ }
+
+ handleClearCompleted (event) {
+ var newTodos = this.state.todos.filter((todo) => { return !todo.done});
+ this.setState({ todos: newTodos });
+ }
+
+ render () {
+ return
+
+
+
+
+ }
+}
diff --git a/ep30-extract-form-into-componenet-from-app/app/components/DisplayItem.jsx b/ep30-extract-form-into-componenet-from-app/app/components/DisplayItem.jsx
new file mode 100644
index 0000000..47d9af2
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/components/DisplayItem.jsx
@@ -0,0 +1,86 @@
+import React from 'react';
+
+var TodoActions = require('../actions/TodoActions');
+
+export default class DisplayItem extends React.Component {
+
+ constructor () {
+ super();
+ this.state = { editing: false }
+ }
+
+ componentDidMount () {
+ this.setState({ changedText: this.props.todo.title });
+ }
+
+ handleEditing (event) {
+ this.setState({ editing: true, changedText: this.props.todo.title });
+ }
+
+ handleEditingDone (event) {
+ if (event.keyCode === 13 ) { // submit
+ this.setState({ editing: false });
+ }
+ }
+
+ handleEditingChange (event) {
+ var _changedText = event.target.value;
+ this.setState({ changedText: _changedText });
+ }
+
+ toggleDone (todo) {
+ if (todo.done) {
+ TodoActions.markTodoUnDone(todo);
+ } else {
+ TodoActions.markTodoDone(todo);
+ }
+ }
+
+ handleDeleteTodoClick (todo) {
+ TodoActions.deleteTodo(todo);
+ }
+
+ render () {
+ var todo = this.props.todo;
+
+ var viewStyle = {};
+ var editStyle = {};
+
+ if (this.state.editing) {
+ viewStyle.display = 'none';
+ } else {
+ editStyle.display = 'none';
+ }
+
+ return
+
+
+
+
+
+
+ [x]
+
+
+
+
+
+ }
+
+}
+
+DisplayItem.propTypes = {
+ todo: React.PropTypes.object.isRequired
+}
diff --git a/ep30-extract-form-into-componenet-from-app/app/components/DisplayList.jsx b/ep30-extract-form-into-componenet-from-app/app/components/DisplayList.jsx
new file mode 100644
index 0000000..3cd852e
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/components/DisplayList.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import DisplayItem from './DisplayItem';
+
+export default class DisplayList extends React.Component {
+
+ render () {
+ return
+ { this.props.todos.map((todo, i) => {
+ return
+ }) }
+
+ }
+
+}
+
+DisplayList.propTypes = {
+ todos: React.PropTypes.array.isRequired
+}
diff --git a/ep30-extract-form-into-componenet-from-app/app/components/TodoForm.jsx b/ep30-extract-form-into-componenet-from-app/app/components/TodoForm.jsx
new file mode 100644
index 0000000..87916ed
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/components/TodoForm.jsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import DisplayList from './DisplayList';
+import DisplayItem from './DisplayItem';
+
+var TodoActions = require('../actions/TodoActions');
+
+export default class TodoForm extends React.Component {
+
+ constructor () {
+ super();
+ this.state = { editing: false }
+ }
+
+ handleSubmit (event) {
+ event.preventDefault();
+
+ var newTodo = { title: this.state.title, done: false };
+
+ TodoActions.addTodo(newTodo);
+ this.setState({ title: '' });
+ }
+
+ handleChange (event) {
+ var title = event.target.value;
+ this.setState({ title: title });
+ }
+
+ render () {
+ return
+
TODO
+
+ ;
+ }
+
+}
diff --git a/ep30-extract-form-into-componenet-from-app/app/dispatcher/AppDispatcher.js b/ep30-extract-form-into-componenet-from-app/app/dispatcher/AppDispatcher.js
new file mode 100644
index 0000000..5231a4e
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/dispatcher/AppDispatcher.js
@@ -0,0 +1,3 @@
+var Dispatcher = require('flux').Dispatcher;
+
+module.exports = new Dispatcher();
diff --git a/ep30-extract-form-into-componenet-from-app/app/main.jsx b/ep30-extract-form-into-componenet-from-app/app/main.jsx
new file mode 100644
index 0000000..aee7cdf
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/main.jsx
@@ -0,0 +1,14 @@
+import './stylesheets/main.css';
+
+import React from 'react';
+import App from './components/App';
+
+main();
+
+function main() {
+ var div = document.createElement('div');
+ div.setAttribute("id", "todoapp");
+ document.body.appendChild(div);
+
+ React.render(, div);
+}
diff --git a/ep30-extract-form-into-componenet-from-app/app/stores/TodoStore.js b/ep30-extract-form-into-componenet-from-app/app/stores/TodoStore.js
new file mode 100644
index 0000000..c4e4f59
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/stores/TodoStore.js
@@ -0,0 +1,86 @@
+var AppDispatcher = require('../dispatcher/AppDispatcher');
+var EventEmitter = require('events').EventEmitter;
+var assign = require('object-assign');
+
+AppDispatcher.register(function(action) {
+
+ switch(action.actionType) {
+ case 'TODO_DONE':
+ console.log("Handling TODO_DONE using dispatcher in store");
+ TodoStore.markTodoDone(action.todo);
+ break;
+
+ case 'TODO_UNDONE':
+ console.log("Handling TODO_UNDONE using dispatcher in store");
+ TodoStore.markTodoUnDone(action.todo);
+ break;
+
+ case 'TODO_DELETE':
+ console.log("Handling TODO_DELETE using dispatcher in store");
+ TodoStore.deleteTodo(action.todo);
+ break;
+
+ case 'TODO_ADD':
+ console.log("Handling TODO_ADD using dispatcher in store");
+ TodoStore.getTodos();
+ break;
+
+ case 'TODO_ALL':
+ console.log("Handling TODO_ALL using dispatcher in store");
+ TodoStore.setTodos(action.todos);
+ break;
+ }
+
+});
+
+var _todos = {};
+var CHANGE_EVENT = 'change';
+
+var TodoStore = assign({}, EventEmitter.prototype, {
+
+ deleteTodo: (todo) => {
+ var newTodos = _todos.filter( (t) => {
+ return t.id != todo.id
+ } )
+ _todos = newTodos;
+ TodoStore.emitChange();
+ },
+
+ markTodoDone: (todo) => {
+ var _todo = _todos.filter((t) => {
+ return t.id === todo.id;
+ })[0];
+
+ _todo.done = true;
+ TodoStore.emitChange();
+ },
+
+ markTodoUnDone: (todo) => {
+ var _todo = _todos.filter((t) => {
+ return t.id === todo.id;
+ })[0];
+
+ _todo.done = false;
+ TodoStore.emitChange();
+ },
+
+ setTodos: (todos) => {
+ _todos = todos;
+ TodoStore.emitChange();
+ },
+
+ getTodos: () => {
+ return _todos;
+ },
+
+ emitChange: function() {
+ this.emit(CHANGE_EVENT);
+ },
+
+ addChangeListener: function (callback) {
+ console.log("registering callback for changelistener");
+ this.on(CHANGE_EVENT, callback);
+ }
+})
+
+module.exports = TodoStore;
diff --git a/ep30-extract-form-into-componenet-from-app/app/stylesheets/main.css b/ep30-extract-form-into-componenet-from-app/app/stylesheets/main.css
new file mode 100644
index 0000000..ac2d81f
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/stylesheets/main.css
@@ -0,0 +1,190 @@
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
+ line-height: 1.4em;
+ background: #eeeeee;
+ color: #333333;
+ width: 520px;
+ margin: 0 auto;
+ -webkit-font-smoothing: antialiased;
+}
+
+#todoapp {
+ background: #fff;
+ padding: 20px;
+ margin-bottom: 40px;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -webkit-border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -ms-border-radius: 0 0 5px 5px;
+ -o-border-radius: 0 0 5px 5px;
+ border-radius: 0 0 5px 5px;
+}
+
+#todoapp h1 {
+ font-size: 36px;
+ font-weight: bold;
+ text-align: center;
+ padding: 0 0 10px 0;
+}
+
+#todoapp input[type="text"] {
+ width: 466px;
+ font-size: 24px;
+ font-family: inherit;
+ line-height: 1.4em;
+ border: 0;
+ outline: none;
+ padding: 6px;
+ border: 1px solid #999999;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+}
+
+#todoapp input::-webkit-input-placeholder {
+ font-style: italic;
+}
+
+#todo-list {
+ margin: 10px 0;
+ padding: 0;
+ list-style: none;
+}
+
+#todo-list li {
+ padding: 18px 20px 18px 0;
+ position: relative;
+ font-size: 24px;
+ border-bottom: 1px solid #cccccc;
+}
+
+#todo-list li:last-child {
+ border-bottom: none;
+}
+
+#todo-list li.done label {
+ color: #777777;
+ text-decoration: line-through;
+}
+
+#todo-list .destroy {
+ position: absolute;
+ right: 5px;
+ top: 20px;
+ display: none;
+ cursor: pointer;
+ width: 20px;
+ height: 20px;
+}
+
+#todo-list li:hover .destroy {
+ display: block;
+}
+
+#todo-list .destroy:hover {
+ background-position: 0 -20px;
+}
+
+#todo-list li.editing {
+ border-bottom: none;
+ margin-top: -1px;
+ padding: 0;
+}
+
+#todo-list li.editing:last-child {
+ margin-bottom: -1px;
+}
+
+#todo-list li .view label {
+ word-break: break-word;
+}
+
+#todoapp footer {
+ margin: 0 -20px -20px -20px;
+ overflow: hidden;
+ color: #555555;
+ background: #f4fce8;
+ border-top: 1px solid #ededed;
+ padding: 0 20px;
+ line-height: 37px;
+ -webkit-border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -ms-border-radius: 0 0 5px 5px;
+ -o-border-radius: 0 0 5px 5px;
+ border-radius: 0 0 5px 5px;
+}
+
+#clear-completed {
+ float: right;
+ line-height: 20px;
+ text-decoration: none;
+ background: rgba(0, 0, 0, 0.1);
+ color: #555555;
+ font-size: 11px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+ padding: 0 10px 1px;
+ cursor: pointer;
+ -webkit-border-radius: 12px;
+ -moz-border-radius: 12px;
+ -ms-border-radius: 12px;
+ -o-border-radius: 12px;
+ border-radius: 12px;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+}
+
+#clear-completed:hover {
+ background: rgba(0, 0, 0, 0.15);
+ -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -ms-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+}
+
+#clear-completed:active {
+ position: relative;
+ top: 1px;
+}
+
+#todo-count span {
+ font-weight: bold;
+}
+
+#instructions {
+ margin: 10px auto;
+ color: #777777;
+ text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
+ text-align: center;
+}
+
+#instructions a {
+ color: #336699;
+}
+
+#credits {
+ margin: 30px auto;
+ color: #999;
+ text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
+ text-align: center;
+}
+
+#credits a {
+ color: #888;
+}
diff --git a/ep30-extract-form-into-componenet-from-app/app/utils/api.js b/ep30-extract-form-into-componenet-from-app/app/utils/api.js
new file mode 100644
index 0000000..1cfd44b
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/utils/api.js
@@ -0,0 +1,62 @@
+require('whatwg-fetch');
+var Constants = require("./constants");
+var HEADER = { 'Accept': 'application/json', 'Content-Type': 'application/json' }
+
+var api = {
+ generateUrlWithApiKey(endpoint) {
+ return Constants.BASE_URL + endpoint + '?api_key=' + Constants.API_KEY;
+ },
+
+ getTodos () {
+ var url = this.generateUrlWithApiKey('todos');
+ return fetch(url)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to get all tasks list.', error) );
+ },
+
+ addTodo (todo) {
+ var url = this.generateUrlWithApiKey('todos');
+ var options = {
+ method: 'POST',
+ headers: HEADER,
+ body: JSON.stringify({todo: todo})
+ };
+ return fetch(url, options)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to add a TODO.', error) );
+ },
+
+ markTodoDone (todo) {
+ var url = this.generateUrlWithApiKey('todos/' + todo.id);
+ var options = {
+ method: 'PUT',
+ headers: HEADER,
+ body: JSON.stringify({done: true})
+ };
+ return fetch(url, options)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to mark task as done. ', error) );
+ },
+
+ markTodoUnDone (todo) {
+ var url = this.generateUrlWithApiKey('todos/' + todo.id);
+ var options = {
+ method: 'PUT',
+ headers: HEADER,
+ body: JSON.stringify({done: false})
+ };
+ return fetch(url, options)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to mark task as undone. ', error) );
+ },
+
+ deleteTodo (idToBeDeleted, processDataCallback) {
+ var url = this.generateUrlWithApiKey('todos/' + idToBeDeleted);
+ return fetch(url, { method: 'DELETE' })
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to delete TODO.', error) );
+ },
+
+};
+
+module.exports = api;
diff --git a/ep30-extract-form-into-componenet-from-app/app/utils/constants.js b/ep30-extract-form-into-componenet-from-app/app/utils/constants.js
new file mode 100644
index 0000000..2affc3c
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/app/utils/constants.js
@@ -0,0 +1,5 @@
+var Constants = {
+ BASE_URL: "http://lrjis-api-production.herokuapp.com/api/v1/",
+ API_KEY: "4d5e466a-97a4-46ba-bb3d-3da6c4347965"
+};
+module.exports = Constants;
diff --git a/ep30-extract-form-into-componenet-from-app/package.json b/ep30-extract-form-into-componenet-from-app/package.json
new file mode 100644
index 0000000..3a4ee8f
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "learning-reactjs-in-steps",
+ "version": "0.0.1",
+ "description": "Learn ReactJS in steps",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "TAGET=build webpack",
+ "start": "TARGET=dev webpack-dev-server --devtool eval --progress --colors --hot"
+ },
+ "author": "Neeraj Singh",
+ "license": "ISC",
+ "devDependencies": {
+ "babel-core": "^5.6.15",
+ "babel-loader": "^5.3.1",
+ "css-loader": "^0.15.1",
+ "html-webpack-plugin": "^1.5.2",
+ "node-libs-browser": "^0.5.2",
+ "react-hot-loader": "^1.2.7",
+ "style-loader": "^0.12.3",
+ "webpack": "^1.10.1",
+ "webpack-dev-server": "^1.10.1",
+ "webpack-merge": "^0.1.2"
+ },
+ "dependencies": {
+ "flux": "^2.1.1",
+ "jquery": "1.11.3",
+ "object-assign": "^4.0.1",
+ "random-key": "^0.3.2",
+ "react": "^0.13.3",
+ "whatwg-fetch": "^0.9.0"
+ }
+}
diff --git a/ep30-extract-form-into-componenet-from-app/webpack.config.js b/ep30-extract-form-into-componenet-from-app/webpack.config.js
new file mode 100644
index 0000000..bb8e6c1
--- /dev/null
+++ b/ep30-extract-form-into-componenet-from-app/webpack.config.js
@@ -0,0 +1,54 @@
+var path = require('path');
+var HtmlWebpackPlugin = require('html-webpack-plugin');
+var merge = require('webpack-merge');
+
+var TARGET = process.env.TARGET;
+var ROOT_PATH = path.resolve(__dirname);
+
+var common = {
+ entry: [path.resolve(ROOT_PATH, 'app/main')],
+ resolve: {
+ extensions: ['', '.js', '.jsx'],
+ },
+ output: {
+ path: path.resolve(ROOT_PATH, 'build'),
+ filename: 'bundle.js',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({ title: 'Todo app', }),
+ ],
+ module: {
+ loaders: [
+ {
+ test: /\.css$/,
+ loaders: ['style', 'css'],
+ },
+ {
+ test: /\.jsx?$/,
+ loader: 'babel?stage=1',
+ include: path.resolve(ROOT_PATH, 'app'),
+ }
+ ],
+ },
+};
+
+if (TARGET === 'build') {
+ module.exports = common;
+}
+
+if (TARGET === 'dev') {
+ module.exports = merge(common, {
+ entry: [
+ 'webpack-dev-server/client?http://0.0.0.0:8080',
+ 'webpack/hot/dev-server'
+ ],
+ module: {
+ loaders: [ {
+ test: /\.jsx?$/,
+ loaders: ['react-hot', 'babel?stage=1'],
+ include: path.resolve(ROOT_PATH, 'app'),
+ },
+ ],
+ },
+ });
+}
diff --git a/ep31-get-all-todos/README.md b/ep31-get-all-todos/README.md
new file mode 100644
index 0000000..2712807
--- /dev/null
+++ b/ep31-get-all-todos/README.md
@@ -0,0 +1,8 @@
+#### Setting up the application
+
+```
+npm install
+npm start
+```
+
+Visit http://localhost:8080 in browser.
diff --git a/ep31-get-all-todos/app/actions/TodoActions.js b/ep31-get-all-todos/app/actions/TodoActions.js
new file mode 100644
index 0000000..649b648
--- /dev/null
+++ b/ep31-get-all-todos/app/actions/TodoActions.js
@@ -0,0 +1,65 @@
+var api = require("../utils/api");
+var TodoStore = require("../stores/TodoStore");
+var AppDispatcher = require('../dispatcher/AppDispatcher');
+
+var TodoActions = {
+
+ addTodo: (todo) => {
+ console.log("adding TODO");
+ api.addTodo(todo)
+ .then( () => {
+ api.getTodos()
+ .then( (responseData) => {
+ var todos = responseData.todos;
+ console.log("All todos", todos);
+ TodoStore.setTodos(todos);
+ })
+ })
+ .then( () => {
+ console.log("Added TODO successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_ADD'
+ });
+ })
+ },
+
+ deleteTodo: (todo) => {
+ console.log("Deleting TODO");
+ api.deleteTodo(todo.id)
+ .then( () => {
+ console.log("Deleted TODO successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_DELETE',
+ todo: todo
+ });
+ })
+ },
+
+ markTodoDone: (todo) => {
+ console.log("Marking TODO as done");
+ api.markTodoDone(todo)
+ .then( () => {
+ console.log("marked TODO as done successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_DONE',
+ todo: todo
+ });
+
+ })
+ },
+
+ markTodoUnDone: (todo) => {
+ console.log("Marking TODO as undone");
+ api.markTodoUnDone(todo)
+ .then( () => {
+ console.log("marked TODO as undone successfully");
+ AppDispatcher.dispatch({
+ actionType: 'TODO_UNDONE',
+ todo: todo
+ });
+ })
+ },
+
+}
+
+module.exports = TodoActions;
diff --git a/ep31-get-all-todos/app/components/App.jsx b/ep31-get-all-todos/app/components/App.jsx
new file mode 100644
index 0000000..dae7061
--- /dev/null
+++ b/ep31-get-all-todos/app/components/App.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import TodoForm from './TodoForm';
+import DisplayList from './DisplayList';
+
+var rand = require('random-key');
+var api = require("../utils/api");
+var TodoActions = require("../actions/TodoActions");
+var TodoStore = require("../stores/TodoStore");
+
+export default class App extends React.Component {
+
+ constructor () {
+ super();
+ this.state = { title: '', todos: [] };
+
+ this.getAllTodos();
+ }
+
+ componentDidMount () {
+ var storeIsTellingUsThatDataHasChanged = () => {
+ console.log("Store is telling us that data has change");
+ var todos = TodoStore.getTodos();
+ console.log("todos is");
+ console.log(todos);
+ this.setState({todos: todos});
+ }
+ TodoStore.addChangeListener(storeIsTellingUsThatDataHasChanged);
+ }
+
+ getAllTodos () {
+ api.getTodos()
+ .then( (responseData) => {
+ var todos = responseData.todos;
+ this.setState({todos: todos });
+ TodoStore.setTodos(todos);
+ })
+ }
+
+ handleClearCompleted (event) {
+ var newTodos = this.state.todos.filter((todo) => { return !todo.done});
+ this.setState({ todos: newTodos });
+ }
+
+ render () {
+ return
+
+
+
+
+ }
+}
diff --git a/ep31-get-all-todos/app/components/DisplayItem.jsx b/ep31-get-all-todos/app/components/DisplayItem.jsx
new file mode 100644
index 0000000..47d9af2
--- /dev/null
+++ b/ep31-get-all-todos/app/components/DisplayItem.jsx
@@ -0,0 +1,86 @@
+import React from 'react';
+
+var TodoActions = require('../actions/TodoActions');
+
+export default class DisplayItem extends React.Component {
+
+ constructor () {
+ super();
+ this.state = { editing: false }
+ }
+
+ componentDidMount () {
+ this.setState({ changedText: this.props.todo.title });
+ }
+
+ handleEditing (event) {
+ this.setState({ editing: true, changedText: this.props.todo.title });
+ }
+
+ handleEditingDone (event) {
+ if (event.keyCode === 13 ) { // submit
+ this.setState({ editing: false });
+ }
+ }
+
+ handleEditingChange (event) {
+ var _changedText = event.target.value;
+ this.setState({ changedText: _changedText });
+ }
+
+ toggleDone (todo) {
+ if (todo.done) {
+ TodoActions.markTodoUnDone(todo);
+ } else {
+ TodoActions.markTodoDone(todo);
+ }
+ }
+
+ handleDeleteTodoClick (todo) {
+ TodoActions.deleteTodo(todo);
+ }
+
+ render () {
+ var todo = this.props.todo;
+
+ var viewStyle = {};
+ var editStyle = {};
+
+ if (this.state.editing) {
+ viewStyle.display = 'none';
+ } else {
+ editStyle.display = 'none';
+ }
+
+ return
+
+
+
+
+
+
+ [x]
+
+
+
+
+
+ }
+
+}
+
+DisplayItem.propTypes = {
+ todo: React.PropTypes.object.isRequired
+}
diff --git a/ep31-get-all-todos/app/components/DisplayList.jsx b/ep31-get-all-todos/app/components/DisplayList.jsx
new file mode 100644
index 0000000..3cd852e
--- /dev/null
+++ b/ep31-get-all-todos/app/components/DisplayList.jsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import DisplayItem from './DisplayItem';
+
+export default class DisplayList extends React.Component {
+
+ render () {
+ return
+ { this.props.todos.map((todo, i) => {
+ return
+ }) }
+
+ }
+
+}
+
+DisplayList.propTypes = {
+ todos: React.PropTypes.array.isRequired
+}
diff --git a/ep31-get-all-todos/app/components/TodoForm.jsx b/ep31-get-all-todos/app/components/TodoForm.jsx
new file mode 100644
index 0000000..87916ed
--- /dev/null
+++ b/ep31-get-all-todos/app/components/TodoForm.jsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import DisplayList from './DisplayList';
+import DisplayItem from './DisplayItem';
+
+var TodoActions = require('../actions/TodoActions');
+
+export default class TodoForm extends React.Component {
+
+ constructor () {
+ super();
+ this.state = { editing: false }
+ }
+
+ handleSubmit (event) {
+ event.preventDefault();
+
+ var newTodo = { title: this.state.title, done: false };
+
+ TodoActions.addTodo(newTodo);
+ this.setState({ title: '' });
+ }
+
+ handleChange (event) {
+ var title = event.target.value;
+ this.setState({ title: title });
+ }
+
+ render () {
+ return
+
TODO
+
+ ;
+ }
+
+}
diff --git a/ep31-get-all-todos/app/dispatcher/AppDispatcher.js b/ep31-get-all-todos/app/dispatcher/AppDispatcher.js
new file mode 100644
index 0000000..5231a4e
--- /dev/null
+++ b/ep31-get-all-todos/app/dispatcher/AppDispatcher.js
@@ -0,0 +1,3 @@
+var Dispatcher = require('flux').Dispatcher;
+
+module.exports = new Dispatcher();
diff --git a/ep31-get-all-todos/app/main.jsx b/ep31-get-all-todos/app/main.jsx
new file mode 100644
index 0000000..aee7cdf
--- /dev/null
+++ b/ep31-get-all-todos/app/main.jsx
@@ -0,0 +1,14 @@
+import './stylesheets/main.css';
+
+import React from 'react';
+import App from './components/App';
+
+main();
+
+function main() {
+ var div = document.createElement('div');
+ div.setAttribute("id", "todoapp");
+ document.body.appendChild(div);
+
+ React.render(, div);
+}
diff --git a/ep31-get-all-todos/app/stores/TodoStore.js b/ep31-get-all-todos/app/stores/TodoStore.js
new file mode 100644
index 0000000..9c5ed9e
--- /dev/null
+++ b/ep31-get-all-todos/app/stores/TodoStore.js
@@ -0,0 +1,81 @@
+var AppDispatcher = require('../dispatcher/AppDispatcher');
+var EventEmitter = require('events').EventEmitter;
+var assign = require('object-assign');
+
+AppDispatcher.register(function(action) {
+
+ switch(action.actionType) {
+ case 'TODO_DONE':
+ console.log("Handling TODO_DONE using dispatcher in store");
+ TodoStore.markTodoDone(action.todo);
+ break;
+
+ case 'TODO_UNDONE':
+ console.log("Handling TODO_UNDONE using dispatcher in store");
+ TodoStore.markTodoUnDone(action.todo);
+ break;
+
+ case 'TODO_DELETE':
+ console.log("Handling TODO_DELETE using dispatcher in store");
+ TodoStore.deleteTodo(action.todo);
+ break;
+
+ case 'TODO_ADD':
+ console.log("Handling TODO_ADD using dispatcher in store");
+ TodoStore.getTodos();
+ break;
+ }
+
+});
+
+var _todos = {};
+var CHANGE_EVENT = 'change';
+
+var TodoStore = assign({}, EventEmitter.prototype, {
+
+ deleteTodo: (todo) => {
+ var newTodos = _todos.filter( (t) => {
+ return t.id != todo.id
+ } )
+ _todos = newTodos;
+ TodoStore.emitChange();
+ },
+
+ markTodoDone: (todo) => {
+ var _todo = _todos.filter((t) => {
+ return t.id === todo.id;
+ })[0];
+
+ _todo.done = true;
+ TodoStore.emitChange();
+ },
+
+ markTodoUnDone: (todo) => {
+ var _todo = _todos.filter((t) => {
+ return t.id === todo.id;
+ })[0];
+
+ _todo.done = false;
+ TodoStore.emitChange();
+ },
+
+ setTodos: (todos) => {
+ _todos = todos;
+ TodoStore.emitChange();
+ },
+
+ getTodos: () => {
+ return _todos;
+ },
+
+ emitChange: function() {
+ this.emit(CHANGE_EVENT);
+ },
+
+ addChangeListener: function (callback) {
+ console.log("registering callback for changelistener");
+ this.on(CHANGE_EVENT, callback);
+ }
+})
+
+module.exports = TodoStore;
diff --git a/ep31-get-all-todos/app/stylesheets/main.css b/ep31-get-all-todos/app/stylesheets/main.css
new file mode 100644
index 0000000..ac2d81f
--- /dev/null
+++ b/ep31-get-all-todos/app/stylesheets/main.css
@@ -0,0 +1,190 @@
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
+ line-height: 1.4em;
+ background: #eeeeee;
+ color: #333333;
+ width: 520px;
+ margin: 0 auto;
+ -webkit-font-smoothing: antialiased;
+}
+
+#todoapp {
+ background: #fff;
+ padding: 20px;
+ margin-bottom: 40px;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
+ -webkit-border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -ms-border-radius: 0 0 5px 5px;
+ -o-border-radius: 0 0 5px 5px;
+ border-radius: 0 0 5px 5px;
+}
+
+#todoapp h1 {
+ font-size: 36px;
+ font-weight: bold;
+ text-align: center;
+ padding: 0 0 10px 0;
+}
+
+#todoapp input[type="text"] {
+ width: 466px;
+ font-size: 24px;
+ font-family: inherit;
+ line-height: 1.4em;
+ border: 0;
+ outline: none;
+ padding: 6px;
+ border: 1px solid #999999;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
+}
+
+#todoapp input::-webkit-input-placeholder {
+ font-style: italic;
+}
+
+#todo-list {
+ margin: 10px 0;
+ padding: 0;
+ list-style: none;
+}
+
+#todo-list li {
+ padding: 18px 20px 18px 0;
+ position: relative;
+ font-size: 24px;
+ border-bottom: 1px solid #cccccc;
+}
+
+#todo-list li:last-child {
+ border-bottom: none;
+}
+
+#todo-list li.done label {
+ color: #777777;
+ text-decoration: line-through;
+}
+
+#todo-list .destroy {
+ position: absolute;
+ right: 5px;
+ top: 20px;
+ display: none;
+ cursor: pointer;
+ width: 20px;
+ height: 20px;
+}
+
+#todo-list li:hover .destroy {
+ display: block;
+}
+
+#todo-list .destroy:hover {
+ background-position: 0 -20px;
+}
+
+#todo-list li.editing {
+ border-bottom: none;
+ margin-top: -1px;
+ padding: 0;
+}
+
+#todo-list li.editing:last-child {
+ margin-bottom: -1px;
+}
+
+#todo-list li .view label {
+ word-break: break-word;
+}
+
+#todoapp footer {
+ margin: 0 -20px -20px -20px;
+ overflow: hidden;
+ color: #555555;
+ background: #f4fce8;
+ border-top: 1px solid #ededed;
+ padding: 0 20px;
+ line-height: 37px;
+ -webkit-border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -ms-border-radius: 0 0 5px 5px;
+ -o-border-radius: 0 0 5px 5px;
+ border-radius: 0 0 5px 5px;
+}
+
+#clear-completed {
+ float: right;
+ line-height: 20px;
+ text-decoration: none;
+ background: rgba(0, 0, 0, 0.1);
+ color: #555555;
+ font-size: 11px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+ padding: 0 10px 1px;
+ cursor: pointer;
+ -webkit-border-radius: 12px;
+ -moz-border-radius: 12px;
+ -ms-border-radius: 12px;
+ -o-border-radius: 12px;
+ border-radius: 12px;
+ -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+ box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
+}
+
+#clear-completed:hover {
+ background: rgba(0, 0, 0, 0.15);
+ -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -ms-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ -o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+ box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
+}
+
+#clear-completed:active {
+ position: relative;
+ top: 1px;
+}
+
+#todo-count span {
+ font-weight: bold;
+}
+
+#instructions {
+ margin: 10px auto;
+ color: #777777;
+ text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
+ text-align: center;
+}
+
+#instructions a {
+ color: #336699;
+}
+
+#credits {
+ margin: 30px auto;
+ color: #999;
+ text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0;
+ text-align: center;
+}
+
+#credits a {
+ color: #888;
+}
diff --git a/ep31-get-all-todos/app/utils/api.js b/ep31-get-all-todos/app/utils/api.js
new file mode 100644
index 0000000..1cfd44b
--- /dev/null
+++ b/ep31-get-all-todos/app/utils/api.js
@@ -0,0 +1,62 @@
+require('whatwg-fetch');
+var Constants = require("./constants");
+var HEADER = { 'Accept': 'application/json', 'Content-Type': 'application/json' }
+
+var api = {
+ generateUrlWithApiKey(endpoint) {
+ return Constants.BASE_URL + endpoint + '?api_key=' + Constants.API_KEY;
+ },
+
+ getTodos () {
+ var url = this.generateUrlWithApiKey('todos');
+ return fetch(url)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to get all tasks list.', error) );
+ },
+
+ addTodo (todo) {
+ var url = this.generateUrlWithApiKey('todos');
+ var options = {
+ method: 'POST',
+ headers: HEADER,
+ body: JSON.stringify({todo: todo})
+ };
+ return fetch(url, options)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to add a TODO.', error) );
+ },
+
+ markTodoDone (todo) {
+ var url = this.generateUrlWithApiKey('todos/' + todo.id);
+ var options = {
+ method: 'PUT',
+ headers: HEADER,
+ body: JSON.stringify({done: true})
+ };
+ return fetch(url, options)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to mark task as done. ', error) );
+ },
+
+ markTodoUnDone (todo) {
+ var url = this.generateUrlWithApiKey('todos/' + todo.id);
+ var options = {
+ method: 'PUT',
+ headers: HEADER,
+ body: JSON.stringify({done: false})
+ };
+ return fetch(url, options)
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to mark task as undone. ', error) );
+ },
+
+ deleteTodo (idToBeDeleted, processDataCallback) {
+ var url = this.generateUrlWithApiKey('todos/' + idToBeDeleted);
+ return fetch(url, { method: 'DELETE' })
+ .then((res) => res.json())
+ .catch( (error) => console.log('Failed to delete TODO.', error) );
+ },
+
+};
+
+module.exports = api;
diff --git a/ep31-get-all-todos/app/utils/constants.js b/ep31-get-all-todos/app/utils/constants.js
new file mode 100644
index 0000000..2affc3c
--- /dev/null
+++ b/ep31-get-all-todos/app/utils/constants.js
@@ -0,0 +1,5 @@
+var Constants = {
+ BASE_URL: "http://lrjis-api-production.herokuapp.com/api/v1/",
+ API_KEY: "4d5e466a-97a4-46ba-bb3d-3da6c4347965"
+};
+module.exports = Constants;
diff --git a/ep31-get-all-todos/package.json b/ep31-get-all-todos/package.json
new file mode 100644
index 0000000..3a4ee8f
--- /dev/null
+++ b/ep31-get-all-todos/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "learning-reactjs-in-steps",
+ "version": "0.0.1",
+ "description": "Learn ReactJS in steps",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "TAGET=build webpack",
+ "start": "TARGET=dev webpack-dev-server --devtool eval --progress --colors --hot"
+ },
+ "author": "Neeraj Singh",
+ "license": "ISC",
+ "devDependencies": {
+ "babel-core": "^5.6.15",
+ "babel-loader": "^5.3.1",
+ "css-loader": "^0.15.1",
+ "html-webpack-plugin": "^1.5.2",
+ "node-libs-browser": "^0.5.2",
+ "react-hot-loader": "^1.2.7",
+ "style-loader": "^0.12.3",
+ "webpack": "^1.10.1",
+ "webpack-dev-server": "^1.10.1",
+ "webpack-merge": "^0.1.2"
+ },
+ "dependencies": {
+ "flux": "^2.1.1",
+ "jquery": "1.11.3",
+ "object-assign": "^4.0.1",
+ "random-key": "^0.3.2",
+ "react": "^0.13.3",
+ "whatwg-fetch": "^0.9.0"
+ }
+}
diff --git a/ep31-get-all-todos/webpack.config.js b/ep31-get-all-todos/webpack.config.js
new file mode 100644
index 0000000..bb8e6c1
--- /dev/null
+++ b/ep31-get-all-todos/webpack.config.js
@@ -0,0 +1,54 @@
+var path = require('path');
+var HtmlWebpackPlugin = require('html-webpack-plugin');
+var merge = require('webpack-merge');
+
+var TARGET = process.env.TARGET;
+var ROOT_PATH = path.resolve(__dirname);
+
+var common = {
+ entry: [path.resolve(ROOT_PATH, 'app/main')],
+ resolve: {
+ extensions: ['', '.js', '.jsx'],
+ },
+ output: {
+ path: path.resolve(ROOT_PATH, 'build'),
+ filename: 'bundle.js',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({ title: 'Todo app', }),
+ ],
+ module: {
+ loaders: [
+ {
+ test: /\.css$/,
+ loaders: ['style', 'css'],
+ },
+ {
+ test: /\.jsx?$/,
+ loader: 'babel?stage=1',
+ include: path.resolve(ROOT_PATH, 'app'),
+ }
+ ],
+ },
+};
+
+if (TARGET === 'build') {
+ module.exports = common;
+}
+
+if (TARGET === 'dev') {
+ module.exports = merge(common, {
+ entry: [
+ 'webpack-dev-server/client?http://0.0.0.0:8080',
+ 'webpack/hot/dev-server'
+ ],
+ module: {
+ loaders: [ {
+ test: /\.jsx?$/,
+ loaders: ['react-hot', 'babel?stage=1'],
+ include: path.resolve(ROOT_PATH, 'app'),
+ },
+ ],
+ },
+ });
+}