diff --git a/.gitignore b/.gitignore
index f8d1970..b7cd026 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
npm-debug.log
**/node_modules/*
build/*
+.idea
diff --git a/ep19-call-api-jquery/README.md b/ep19-call-api-jquery/README.md
new file mode 100644
index 0000000..0f6225e
--- /dev/null
+++ b/ep19-call-api-jquery/README.md
@@ -0,0 +1,13 @@
+#### Setting up the application
+
+```
+npm install
+npm start
+```
+
+Visit http://localhost:8080 in browser.
+
+#### Notes
+
+* `npm install random-key --save`
+* [Source code](...)
diff --git a/ep19-call-api-jquery/app/components/App.jsx b/ep19-call-api-jquery/app/components/App.jsx
new file mode 100644
index 0000000..9bae099
--- /dev/null
+++ b/ep19-call-api-jquery/app/components/App.jsx
@@ -0,0 +1,97 @@
+import React from 'react';
+import DisplayList from './DisplayList';
+
+var rand = require('random-key');
+var api = require("../utils/api");
+
+export default class App extends React.Component {
+
+ constructor () {
+ super();
+ this.state = { title: '', todos: [] };
+ var processData = function(data) {
+ this.setState({todos: data.todos});
+ };
+ api.getTasks(processData.bind(this));
+ }
+
+ handleDone (idToBeMarkedAsDone) {
+ var _todos = this.state.todos;
+ var todo = _todos.filter((todo) => {
+ return todo.id === idToBeMarkedAsDone;
+ })[0];
+
+ todo.done = !todo.done;
+
+ var processData = function(data) {
+ this.setState({todos: data.todos});
+ };
+
+ var markTaskDoneCallback = function(data){
+ data.success ? api.getTasks(processData.bind(this)) : console.log("Failed to mark task as done/undone");
+ };
+
+ api.markTaskDone(markTaskDoneCallback.bind(this), todo);
+ }
+
+ handleDelete (idToBeDeleted) {
+ var processData = function(data) {
+ this.setState({todos: data.todos});
+ };
+
+ var deleteTaskCallback = function(data){
+ data.success ? api.getTasks(processData.bind(this)) : console.log("Failed to delete task");
+ };
+
+ api.deleteTask(deleteTaskCallback.bind(this), idToBeDeleted);
+ }
+
+ handleSubmit (event) {
+ event.preventDefault();
+
+ var newTodo = { title: this.state.title, done: false };
+
+ var processData = function(data) {
+ this.setState({title: '', todos: data.todos});
+ };
+
+ var addTaskCallback = function(data){
+ data.success ? api.getTasks(processData.bind(this)) : console.log("Failed to add task");
+ };
+
+ api.addTask(addTaskCallback.bind(this), newTodo);
+ }
+
+ 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/ep19-call-api-jquery/app/components/DisplayItem.jsx b/ep19-call-api-jquery/app/components/DisplayItem.jsx
new file mode 100644
index 0000000..e91c72b
--- /dev/null
+++ b/ep19-call-api-jquery/app/components/DisplayItem.jsx
@@ -0,0 +1,74 @@
+import React from 'react';
+
+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 });
+ }
+
+ 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,
+ handleDone: React.PropTypes.func.isRequired,
+ handleDelete: React.PropTypes.func.isRequired
+}
diff --git a/ep19-call-api-jquery/app/components/DisplayList.jsx b/ep19-call-api-jquery/app/components/DisplayList.jsx
new file mode 100644
index 0000000..0b4daec
--- /dev/null
+++ b/ep19-call-api-jquery/app/components/DisplayList.jsx
@@ -0,0 +1,25 @@
+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,
+ handleDone: React.PropTypes.func.isRequired,
+ handleDelete: React.PropTypes.func.isRequired
+}
diff --git a/ep19-call-api-jquery/app/main.jsx b/ep19-call-api-jquery/app/main.jsx
new file mode 100644
index 0000000..aee7cdf
--- /dev/null
+++ b/ep19-call-api-jquery/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/ep19-call-api-jquery/app/stylesheets/main.css b/ep19-call-api-jquery/app/stylesheets/main.css
new file mode 100644
index 0000000..ac2d81f
--- /dev/null
+++ b/ep19-call-api-jquery/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/ep19-call-api-jquery/app/utils/api.js b/ep19-call-api-jquery/app/utils/api.js
new file mode 100644
index 0000000..8e5a67d
--- /dev/null
+++ b/ep19-call-api-jquery/app/utils/api.js
@@ -0,0 +1,46 @@
+var Constants = require("./constants");
+var $ = require('jquery');
+
+var api = {
+ getTasks (processData) {
+ var url = Constants.BASE_URL + 'todos';
+ this.makeAjaxCall(url, 'GET', processData)
+ },
+
+ markTaskDone (processData, todo) {
+ var url = Constants.BASE_URL + 'todos/' + todo.id;
+ var params = { done: todo.done };
+ this.makeAjaxCall(url, 'PUT', processData, params)
+ },
+
+ deleteTask (processData, idToBeDeleted) {
+ var url = Constants.BASE_URL + 'todos/' + idToBeDeleted;
+ this.makeAjaxCall(url, 'DELETE', processData)
+ },
+
+ addTask (processData, todo) {
+ var url = Constants.BASE_URL + 'todos';
+ this.makeAjaxCall(url, 'POST', processData, todo)
+ },
+
+ makeAjaxCall (url, type, processDataCallback, params) {
+ $.ajax({
+ type: type,
+ url: url,
+ data: {
+ api_key: Constants.API_KEY,
+ todo: params
+ },
+ dataType: 'json',
+ success: function(data) {
+ console.log(data);
+ processDataCallback(data);
+ },
+ error: function() {
+ console.log("An error has occurred");
+ }
+ });
+ }
+};
+
+module.exports = api;
diff --git a/ep19-call-api-jquery/app/utils/constants.js b/ep19-call-api-jquery/app/utils/constants.js
new file mode 100644
index 0000000..9c0170d
--- /dev/null
+++ b/ep19-call-api-jquery/app/utils/constants.js
@@ -0,0 +1,6 @@
+var Constants = {
+ BASE_URL: "http://lrjis-api-production.herokuapp.com/api/v1/",
+ API_KEY: "add your api key here"
+};
+
+module.exports = Constants;
diff --git a/ep19-call-api-jquery/package.json b/ep19-call-api-jquery/package.json
new file mode 100644
index 0000000..cc1023e
--- /dev/null
+++ b/ep19-call-api-jquery/package.json
@@ -0,0 +1,30 @@
+{
+ "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": {
+ "jquery": "1.11.3",
+ "random-key": "^0.3.2",
+ "react": "^0.13.3"
+ }
+}
diff --git a/ep19-call-api-jquery/webpack.config.js b/ep19-call-api-jquery/webpack.config.js
new file mode 100644
index 0000000..bb8e6c1
--- /dev/null
+++ b/ep19-call-api-jquery/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'),
+ },
+ ],
+ },
+ });
+}