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/ep19-call-api-jquery/app/utils/api.js b/ep19-call-api-jquery/app/utils/api.js index 9c8eebb..e01120b 100644 --- a/ep19-call-api-jquery/app/utils/api.js +++ b/ep19-call-api-jquery/app/utils/api.js @@ -22,7 +22,7 @@ var api = { var url = Constants.BASE_URL + 'todos/' + idToBeDeleted; this.makeAjaxCall(url, 'DELETE', {}, processDataCallback) }, - + makeAjaxCall (url, type, params, processDataCallback) { $.ajax({ type: type, diff --git a/ep20-using-fetch/README.md b/ep20-using-fetch/README.md new file mode 100644 index 0000000..a404b18 --- /dev/null +++ b/ep20-using-fetch/README.md @@ -0,0 +1,12 @@ +#### Setting up the application + +``` +npm install +npm start +``` + +Visit http://localhost:8080 in browser. + +#### Notes + +* `npm install fetch --save` diff --git a/ep20-using-fetch/app/components/App.jsx b/ep20-using-fetch/app/components/App.jsx new file mode 100644 index 0000000..0da96d8 --- /dev/null +++ b/ep20-using-fetch/app/components/App.jsx @@ -0,0 +1,82 @@ +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: [] }; + + api.getTasks() + .then( (responseData) => this.setState({todos: responseData.todos} )) + .catch( (error) => console.log('Failed to get tasks: ', error) ); + } + + handleDone (idToBeMarkedAsDone) { + var _todos = this.state.todos; + var todo = _todos.filter((todo) => { + return todo.id === idToBeMarkedAsDone; + })[0]; + + todo.done = !todo.done; + + api.markTaskDone(todo) + .then( () => { return api.getTasks() }) + .then( (responseData) => this.setState({todos: responseData.todos} )) + .catch( (error) => console.log('Failed to mark task as done/undone: ', error) ); + } + + handleDelete (idToBeDeleted) { + api.deleteTask(idToBeDeleted) + .then( () => { return api.getTasks() }) + .then( (responseData) => this.setState({todos: responseData.todos} )) + .catch( (error) => console.log('Failed to delete task: ', error) ); + } + + handleSubmit (event) { + event.preventDefault(); + + var newTodo = { title: this.state.title, done: false }; + + api.addTask(newTodo) + .then( () => { return api.getTasks() }) + .then( (responseData) => this.setState({title: '', todos: responseData.todos} )) + .catch( (error) => console.log('Failed to add task: ', error) ); + } + + 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/ep20-using-fetch/app/components/DisplayItem.jsx b/ep20-using-fetch/app/components/DisplayItem.jsx new file mode 100644 index 0000000..e91c72b --- /dev/null +++ b/ep20-using-fetch/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/ep20-using-fetch/app/components/DisplayList.jsx b/ep20-using-fetch/app/components/DisplayList.jsx new file mode 100644 index 0000000..0b4daec --- /dev/null +++ b/ep20-using-fetch/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 + } + +} + +DisplayList.propTypes = { + todos: React.PropTypes.array.isRequired, + handleDone: React.PropTypes.func.isRequired, + handleDelete: React.PropTypes.func.isRequired +} diff --git a/ep20-using-fetch/app/main.jsx b/ep20-using-fetch/app/main.jsx new file mode 100644 index 0000000..aee7cdf --- /dev/null +++ b/ep20-using-fetch/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/ep20-using-fetch/app/stylesheets/main.css b/ep20-using-fetch/app/stylesheets/main.css new file mode 100644 index 0000000..ac2d81f --- /dev/null +++ b/ep20-using-fetch/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/ep20-using-fetch/app/utils/api.js b/ep20-using-fetch/app/utils/api.js new file mode 100644 index 0000000..496ecca --- /dev/null +++ b/ep20-using-fetch/app/utils/api.js @@ -0,0 +1,46 @@ +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; + }, + + getTasks () { + var url = this.generateUrlWithApiKey('todos'); + return fetch(url) + .then((res) => res.json()); + }, + + addTask (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()); + }, + + markTaskDone (todo) { + var url = this.generateUrlWithApiKey('todos/' + todo.id); + var options = { + method: 'PUT', + headers: HEADER, + body: JSON.stringify({done: todo.done}) + }; + return fetch(url, options) + .then((res) => res.json()); + }, + + deleteTask (idToBeDeleted, processDataCallback) { + var url = this.generateUrlWithApiKey('todos/' + idToBeDeleted); + return fetch(url, { method: 'DELETE' }) + .then((res) => res.json()); + }, + +}; + +module.exports = api; diff --git a/ep20-using-fetch/app/utils/constants.js b/ep20-using-fetch/app/utils/constants.js new file mode 100644 index 0000000..2affc3c --- /dev/null +++ b/ep20-using-fetch/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/ep20-using-fetch/package.json b/ep20-using-fetch/package.json new file mode 100644 index 0000000..dd24820 --- /dev/null +++ b/ep20-using-fetch/package.json @@ -0,0 +1,31 @@ +{ + "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", + "whatwg-fetch": "^0.9.0" + } +} diff --git a/ep20-using-fetch/webpack.config.js b/ep20-using-fetch/webpack.config.js new file mode 100644 index 0000000..bb8e6c1 --- /dev/null +++ b/ep20-using-fetch/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/ep21-general-refactoring/README.md b/ep21-general-refactoring/README.md new file mode 100644 index 0000000..2712807 --- /dev/null +++ b/ep21-general-refactoring/README.md @@ -0,0 +1,8 @@ +#### Setting up the application + +``` +npm install +npm start +``` + +Visit http://localhost:8080 in browser. diff --git a/ep21-general-refactoring/app/components/App.jsx b/ep21-general-refactoring/app/components/App.jsx new file mode 100644 index 0000000..caba657 --- /dev/null +++ b/ep21-general-refactoring/app/components/App.jsx @@ -0,0 +1,100 @@ +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: [] }; + + this.getAllTodos(); + } + + getAllTodos () { + api.getTodos() + .then( (responseData) => this.setState({todos: responseData.todos} )) + } + + toggleDone (idToBeMarkedAsDone) { + var _todos = this.state.todos; + var todo = _todos.filter((todo) => { + return todo.id === idToBeMarkedAsDone; + })[0]; + + todo.done = !todo.done; + + if (todo.done) { + this.markTodoDone(todo); + } else { + this.markTodoUnDone(todo); + } + } + + markTodoDone (todo) { + api.markTodoDone(todo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({todos: responseData.todos} )); + } + + markTodoUnDone (todo) { + api.markTodoUnDone(todo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({todos: responseData.todos} )); + } + + handleDelete (idToBeDeleted) { + api.deleteTodo(idToBeDeleted) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({todos: responseData.todos} )); + } + + handleSubmit (event) { + event.preventDefault(); + + var newTodo = { title: this.state.title, done: false }; + + this.addTodo(newTodo); + } + + addTodo (newTodo) { + api.addTodo(newTodo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({title: '', todos: responseData.todos} )); + } + + 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

    +
    + +
    + + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    ; + } +} diff --git a/ep21-general-refactoring/app/components/DisplayItem.jsx b/ep21-general-refactoring/app/components/DisplayItem.jsx new file mode 100644 index 0000000..9c040d4 --- /dev/null +++ b/ep21-general-refactoring/app/components/DisplayItem.jsx @@ -0,0 +1,78 @@ +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 }); + } + + toggleDone (todo) { + this.props.toggleDone(todo.id); + } + + 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, + toggleDone: React.PropTypes.func.isRequired, + handleDelete: React.PropTypes.func.isRequired +} diff --git a/ep21-general-refactoring/app/components/DisplayList.jsx b/ep21-general-refactoring/app/components/DisplayList.jsx new file mode 100644 index 0000000..8af5d22 --- /dev/null +++ b/ep21-general-refactoring/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 + } + +} + +DisplayList.propTypes = { + todos: React.PropTypes.array.isRequired, + toggleDone: React.PropTypes.func.isRequired, + handleDelete: React.PropTypes.func.isRequired +} diff --git a/ep21-general-refactoring/app/main.jsx b/ep21-general-refactoring/app/main.jsx new file mode 100644 index 0000000..aee7cdf --- /dev/null +++ b/ep21-general-refactoring/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/ep21-general-refactoring/app/stylesheets/main.css b/ep21-general-refactoring/app/stylesheets/main.css new file mode 100644 index 0000000..ac2d81f --- /dev/null +++ b/ep21-general-refactoring/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/ep21-general-refactoring/app/utils/api.js b/ep21-general-refactoring/app/utils/api.js new file mode 100644 index 0000000..1cfd44b --- /dev/null +++ b/ep21-general-refactoring/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/ep21-general-refactoring/app/utils/constants.js b/ep21-general-refactoring/app/utils/constants.js new file mode 100644 index 0000000..2affc3c --- /dev/null +++ b/ep21-general-refactoring/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/ep21-general-refactoring/package.json b/ep21-general-refactoring/package.json new file mode 100644 index 0000000..dd24820 --- /dev/null +++ b/ep21-general-refactoring/package.json @@ -0,0 +1,31 @@ +{ + "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", + "whatwg-fetch": "^0.9.0" + } +} diff --git a/ep21-general-refactoring/webpack.config.js b/ep21-general-refactoring/webpack.config.js new file mode 100644 index 0000000..bb8e6c1 --- /dev/null +++ b/ep21-general-refactoring/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/ep22-building-todoactions/README.md b/ep22-building-todoactions/README.md new file mode 100644 index 0000000..2712807 --- /dev/null +++ b/ep22-building-todoactions/README.md @@ -0,0 +1,8 @@ +#### Setting up the application + +``` +npm install +npm start +``` + +Visit http://localhost:8080 in browser. diff --git a/ep22-building-todoactions/app/actions/TodoActions.js b/ep22-building-todoactions/app/actions/TodoActions.js new file mode 100644 index 0000000..53744c3 --- /dev/null +++ b/ep22-building-todoactions/app/actions/TodoActions.js @@ -0,0 +1,33 @@ +var api = require("../utils/api"); + +var TodoActions = { + + markTodoDone: (todo) => { + console.log("Marking TODO as done"); + api.markTodoDone(todo) + .then( () => { + console.log("marked TODO as done successfully"); + TodoActions.getAllTodos(); + }) + }, + + markTodoUnDone: (todo) => { + console.log("Marking TODO as undone"); + api.markTodoUnDone(todo) + .then( () => { + console.log("marked TODO as undone successfully"); + TodoActions.getAllTodos(); + }) + }, + + getAllTodos: () => { + api.getTodos() + .then( (responseData) => { + var todos = responseData.todos; + console.log("new todos", todos); + }) + } + +} + +module.exports = TodoActions; diff --git a/ep22-building-todoactions/app/components/App.jsx b/ep22-building-todoactions/app/components/App.jsx new file mode 100644 index 0000000..caba657 --- /dev/null +++ b/ep22-building-todoactions/app/components/App.jsx @@ -0,0 +1,100 @@ +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: [] }; + + this.getAllTodos(); + } + + getAllTodos () { + api.getTodos() + .then( (responseData) => this.setState({todos: responseData.todos} )) + } + + toggleDone (idToBeMarkedAsDone) { + var _todos = this.state.todos; + var todo = _todos.filter((todo) => { + return todo.id === idToBeMarkedAsDone; + })[0]; + + todo.done = !todo.done; + + if (todo.done) { + this.markTodoDone(todo); + } else { + this.markTodoUnDone(todo); + } + } + + markTodoDone (todo) { + api.markTodoDone(todo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({todos: responseData.todos} )); + } + + markTodoUnDone (todo) { + api.markTodoUnDone(todo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({todos: responseData.todos} )); + } + + handleDelete (idToBeDeleted) { + api.deleteTodo(idToBeDeleted) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({todos: responseData.todos} )); + } + + handleSubmit (event) { + event.preventDefault(); + + var newTodo = { title: this.state.title, done: false }; + + this.addTodo(newTodo); + } + + addTodo (newTodo) { + api.addTodo(newTodo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({title: '', todos: responseData.todos} )); + } + + 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

    +
    + +
    + + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    ; + } +} diff --git a/ep22-building-todoactions/app/components/DisplayItem.jsx b/ep22-building-todoactions/app/components/DisplayItem.jsx new file mode 100644 index 0000000..f0caa98 --- /dev/null +++ b/ep22-building-todoactions/app/components/DisplayItem.jsx @@ -0,0 +1,87 @@ +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); + } + + this.props.toggleDone(todo.id); + } + + 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, + toggleDone: React.PropTypes.func.isRequired, + handleDelete: React.PropTypes.func.isRequired +} diff --git a/ep22-building-todoactions/app/components/DisplayList.jsx b/ep22-building-todoactions/app/components/DisplayList.jsx new file mode 100644 index 0000000..8af5d22 --- /dev/null +++ b/ep22-building-todoactions/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 + } + +} + +DisplayList.propTypes = { + todos: React.PropTypes.array.isRequired, + toggleDone: React.PropTypes.func.isRequired, + handleDelete: React.PropTypes.func.isRequired +} diff --git a/ep22-building-todoactions/app/main.jsx b/ep22-building-todoactions/app/main.jsx new file mode 100644 index 0000000..aee7cdf --- /dev/null +++ b/ep22-building-todoactions/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/ep22-building-todoactions/app/stylesheets/main.css b/ep22-building-todoactions/app/stylesheets/main.css new file mode 100644 index 0000000..ac2d81f --- /dev/null +++ b/ep22-building-todoactions/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/ep22-building-todoactions/app/utils/api.js b/ep22-building-todoactions/app/utils/api.js new file mode 100644 index 0000000..1cfd44b --- /dev/null +++ b/ep22-building-todoactions/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/ep22-building-todoactions/app/utils/constants.js b/ep22-building-todoactions/app/utils/constants.js new file mode 100644 index 0000000..2affc3c --- /dev/null +++ b/ep22-building-todoactions/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/ep22-building-todoactions/package.json b/ep22-building-todoactions/package.json new file mode 100644 index 0000000..dd24820 --- /dev/null +++ b/ep22-building-todoactions/package.json @@ -0,0 +1,31 @@ +{ + "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", + "whatwg-fetch": "^0.9.0" + } +} diff --git a/ep22-building-todoactions/webpack.config.js b/ep22-building-todoactions/webpack.config.js new file mode 100644 index 0000000..bb8e6c1 --- /dev/null +++ b/ep22-building-todoactions/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/ep23-building-todostore/README.md b/ep23-building-todostore/README.md new file mode 100644 index 0000000..2712807 --- /dev/null +++ b/ep23-building-todostore/README.md @@ -0,0 +1,8 @@ +#### Setting up the application + +``` +npm install +npm start +``` + +Visit http://localhost:8080 in browser. diff --git a/ep23-building-todostore/app/actions/TodoActions.js b/ep23-building-todostore/app/actions/TodoActions.js new file mode 100644 index 0000000..18f3222 --- /dev/null +++ b/ep23-building-todostore/app/actions/TodoActions.js @@ -0,0 +1,35 @@ +var api = require("../utils/api"); +var TodoStore = require("../stores/TodoStore"); + +var TodoActions = { + + markTodoDone: (todo) => { + console.log("Marking TODO as done"); + api.markTodoDone(todo) + .then( () => { + console.log("marked TODO as done successfully"); + TodoActions.getAllTodos(); + }) + }, + + markTodoUnDone: (todo) => { + console.log("Marking TODO as undone"); + api.markTodoUnDone(todo) + .then( () => { + console.log("marked TODO as undone successfully"); + TodoActions.getAllTodos(); + }) + }, + + getAllTodos: () => { + api.getTodos() + .then( (responseData) => { + var todos = responseData.todos; + console.log("new todos", todos); + TodoStore.setTodos(todos); + }) + } + +} + +module.exports = TodoActions; diff --git a/ep23-building-todostore/app/components/App.jsx b/ep23-building-todostore/app/components/App.jsx new file mode 100644 index 0000000..026dffb --- /dev/null +++ b/ep23-building-todostore/app/components/App.jsx @@ -0,0 +1,109 @@ +import React from 'react'; +import DisplayList from './DisplayList'; + +var rand = require('random-key'); +var api = require("../utils/api"); +var TodoStore = require("../stores/TodoStore"); + +export default class App extends React.Component { + + constructor () { + super(); + this.state = { title: '', todos: [] }; + + this.getAllTodos(); + } + + componentDidMount () { + var storeIsTellingUsThatDataHasChanged = (todos) => { + console.log("Store is telling us that data has change"); + this.setState({todos: todos}); + } + TodoStore.addChangeListener(storeIsTellingUsThatDataHasChanged); + } + + getAllTodos () { + api.getTodos() + .then( (responseData) => this.setState({todos: responseData.todos} )) + } + + toggleDone (idToBeMarkedAsDone) { + var _todos = this.state.todos; + var todo = _todos.filter((todo) => { + return todo.id === idToBeMarkedAsDone; + })[0]; + + todo.done = !todo.done; + + if (todo.done) { + this.markTodoDone(todo); + } else { + this.markTodoUnDone(todo); + } + } + + markTodoDone (todo) { + api.markTodoDone(todo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({todos: responseData.todos} )); + } + + markTodoUnDone (todo) { + api.markTodoUnDone(todo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({todos: responseData.todos} )); + } + + handleDelete (idToBeDeleted) { + api.deleteTodo(idToBeDeleted) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({todos: responseData.todos} )); + } + + handleSubmit (event) { + event.preventDefault(); + + var newTodo = { title: this.state.title, done: false }; + + this.addTodo(newTodo); + } + + addTodo (newTodo) { + api.addTodo(newTodo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({title: '', todos: responseData.todos} )); + } + + 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

    +
    + +
    + + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    ; + } +} diff --git a/ep23-building-todostore/app/components/DisplayItem.jsx b/ep23-building-todostore/app/components/DisplayItem.jsx new file mode 100644 index 0000000..5e5b56c --- /dev/null +++ b/ep23-building-todostore/app/components/DisplayItem.jsx @@ -0,0 +1,87 @@ +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); + } + + //this.props.toggleDone(todo.id); + } + + 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, + toggleDone: React.PropTypes.func.isRequired, + handleDelete: React.PropTypes.func.isRequired +} diff --git a/ep23-building-todostore/app/components/DisplayList.jsx b/ep23-building-todostore/app/components/DisplayList.jsx new file mode 100644 index 0000000..8af5d22 --- /dev/null +++ b/ep23-building-todostore/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, + toggleDone: React.PropTypes.func.isRequired, + handleDelete: React.PropTypes.func.isRequired +} diff --git a/ep23-building-todostore/app/main.jsx b/ep23-building-todostore/app/main.jsx new file mode 100644 index 0000000..aee7cdf --- /dev/null +++ b/ep23-building-todostore/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/ep23-building-todostore/app/stores/TodoStore.js b/ep23-building-todostore/app/stores/TodoStore.js new file mode 100644 index 0000000..e957043 --- /dev/null +++ b/ep23-building-todostore/app/stores/TodoStore.js @@ -0,0 +1,22 @@ +var _todos = {}; +var _callback; + +var TodoStore = { + + setTodos: (todos) => { + _todos = todos; + console.log("TodoStore", TodoStore.getTodos()); + _callback(todos); + }, + + getTodos: () => { + return _todos; + }, + + addChangeListener: function (callback) { + console.log("registering callback for changelistener"); + _callback = callback; + } +} + +module.exports = TodoStore; diff --git a/ep23-building-todostore/app/stylesheets/main.css b/ep23-building-todostore/app/stylesheets/main.css new file mode 100644 index 0000000..ac2d81f --- /dev/null +++ b/ep23-building-todostore/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/ep23-building-todostore/app/utils/api.js b/ep23-building-todostore/app/utils/api.js new file mode 100644 index 0000000..1cfd44b --- /dev/null +++ b/ep23-building-todostore/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/ep23-building-todostore/app/utils/constants.js b/ep23-building-todostore/app/utils/constants.js new file mode 100644 index 0000000..2affc3c --- /dev/null +++ b/ep23-building-todostore/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/ep23-building-todostore/package.json b/ep23-building-todostore/package.json new file mode 100644 index 0000000..dd24820 --- /dev/null +++ b/ep23-building-todostore/package.json @@ -0,0 +1,31 @@ +{ + "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", + "whatwg-fetch": "^0.9.0" + } +} diff --git a/ep23-building-todostore/webpack.config.js b/ep23-building-todostore/webpack.config.js new file mode 100644 index 0000000..bb8e6c1 --- /dev/null +++ b/ep23-building-todostore/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/ep24-delete-todo-using-flux/README.md b/ep24-delete-todo-using-flux/README.md new file mode 100644 index 0000000..2712807 --- /dev/null +++ b/ep24-delete-todo-using-flux/README.md @@ -0,0 +1,8 @@ +#### Setting up the application + +``` +npm install +npm start +``` + +Visit http://localhost:8080 in browser. diff --git a/ep24-delete-todo-using-flux/app/actions/TodoActions.js b/ep24-delete-todo-using-flux/app/actions/TodoActions.js new file mode 100644 index 0000000..f1768d0 --- /dev/null +++ b/ep24-delete-todo-using-flux/app/actions/TodoActions.js @@ -0,0 +1,44 @@ +var api = require("../utils/api"); +var TodoStore = require("../stores/TodoStore"); + +var TodoActions = { + + deleteTodo: (todo) => { + console.log("Deleting TODO"); + api.deleteTodo(todo.id) + .then( () => { + console.log("Deleted TODO successfully"); + TodoActions.getAllTodos(); + }) + }, + + markTodoDone: (todo) => { + console.log("Marking TODO as done"); + api.markTodoDone(todo) + .then( () => { + console.log("marked TODO as done successfully"); + TodoActions.getAllTodos(); + }) + }, + + markTodoUnDone: (todo) => { + console.log("Marking TODO as undone"); + api.markTodoUnDone(todo) + .then( () => { + console.log("marked TODO as undone successfully"); + TodoActions.getAllTodos(); + }) + }, + + getAllTodos: () => { + api.getTodos() + .then( (responseData) => { + var todos = responseData.todos; + console.log("new todos", todos); + TodoStore.setTodos(todos); + }) + } + +} + +module.exports = TodoActions; diff --git a/ep24-delete-todo-using-flux/app/components/App.jsx b/ep24-delete-todo-using-flux/app/components/App.jsx new file mode 100644 index 0000000..b2a35b4 --- /dev/null +++ b/ep24-delete-todo-using-flux/app/components/App.jsx @@ -0,0 +1,80 @@ +import React from 'react'; +import DisplayList from './DisplayList'; + +var rand = require('random-key'); +var api = require("../utils/api"); +var TodoStore = require("../stores/TodoStore"); + +export default class App extends React.Component { + + constructor () { + super(); + this.state = { title: '', todos: [] }; + + this.getAllTodos(); + } + + componentDidMount () { + var storeIsTellingUsThatDataHasChanged = (todos) => { + console.log("Store is telling us that data has change"); + this.setState({todos: todos}); + } + TodoStore.addChangeListener(storeIsTellingUsThatDataHasChanged); + } + + getAllTodos () { + api.getTodos() + .then( (responseData) => this.setState({todos: responseData.todos} )) + } + + markTodoUnDone (todo) { + api.markTodoUnDone(todo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({todos: responseData.todos} )); + } + + handleSubmit (event) { + event.preventDefault(); + + var newTodo = { title: this.state.title, done: false }; + + this.addTodo(newTodo); + } + + addTodo (newTodo) { + api.addTodo(newTodo) + .then( () => { return api.getTodos() }) + .then( (responseData) => this.setState({title: '', todos: responseData.todos} )); + } + + 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

    +
    + +
    + + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    ; + } +} diff --git a/ep24-delete-todo-using-flux/app/components/DisplayItem.jsx b/ep24-delete-todo-using-flux/app/components/DisplayItem.jsx new file mode 100644 index 0000000..47d9af2 --- /dev/null +++ b/ep24-delete-todo-using-flux/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/ep24-delete-todo-using-flux/app/components/DisplayList.jsx b/ep24-delete-todo-using-flux/app/components/DisplayList.jsx new file mode 100644 index 0000000..3cd852e --- /dev/null +++ b/ep24-delete-todo-using-flux/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/ep24-delete-todo-using-flux/app/main.jsx b/ep24-delete-todo-using-flux/app/main.jsx new file mode 100644 index 0000000..aee7cdf --- /dev/null +++ b/ep24-delete-todo-using-flux/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/ep24-delete-todo-using-flux/app/stores/TodoStore.js b/ep24-delete-todo-using-flux/app/stores/TodoStore.js new file mode 100644 index 0000000..e957043 --- /dev/null +++ b/ep24-delete-todo-using-flux/app/stores/TodoStore.js @@ -0,0 +1,22 @@ +var _todos = {}; +var _callback; + +var TodoStore = { + + setTodos: (todos) => { + _todos = todos; + console.log("TodoStore", TodoStore.getTodos()); + _callback(todos); + }, + + getTodos: () => { + return _todos; + }, + + addChangeListener: function (callback) { + console.log("registering callback for changelistener"); + _callback = callback; + } +} + +module.exports = TodoStore; diff --git a/ep24-delete-todo-using-flux/app/stylesheets/main.css b/ep24-delete-todo-using-flux/app/stylesheets/main.css new file mode 100644 index 0000000..ac2d81f --- /dev/null +++ b/ep24-delete-todo-using-flux/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/ep24-delete-todo-using-flux/app/utils/api.js b/ep24-delete-todo-using-flux/app/utils/api.js new file mode 100644 index 0000000..1cfd44b --- /dev/null +++ b/ep24-delete-todo-using-flux/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/ep24-delete-todo-using-flux/app/utils/constants.js b/ep24-delete-todo-using-flux/app/utils/constants.js new file mode 100644 index 0000000..2affc3c --- /dev/null +++ b/ep24-delete-todo-using-flux/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/ep24-delete-todo-using-flux/package.json b/ep24-delete-todo-using-flux/package.json new file mode 100644 index 0000000..dd24820 --- /dev/null +++ b/ep24-delete-todo-using-flux/package.json @@ -0,0 +1,31 @@ +{ + "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", + "whatwg-fetch": "^0.9.0" + } +} diff --git a/ep24-delete-todo-using-flux/webpack.config.js b/ep24-delete-todo-using-flux/webpack.config.js new file mode 100644 index 0000000..bb8e6c1 --- /dev/null +++ b/ep24-delete-todo-using-flux/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/ep25-add-todo-using-flux/README.md b/ep25-add-todo-using-flux/README.md new file mode 100644 index 0000000..2712807 --- /dev/null +++ b/ep25-add-todo-using-flux/README.md @@ -0,0 +1,8 @@ +#### Setting up the application + +``` +npm install +npm start +``` + +Visit http://localhost:8080 in browser. diff --git a/ep25-add-todo-using-flux/app/actions/TodoActions.js b/ep25-add-todo-using-flux/app/actions/TodoActions.js new file mode 100644 index 0000000..d251f5a --- /dev/null +++ b/ep25-add-todo-using-flux/app/actions/TodoActions.js @@ -0,0 +1,53 @@ +var api = require("../utils/api"); +var TodoStore = require("../stores/TodoStore"); + +var TodoActions = { + + addTodo: (todo) => { + console.log("adding TODO"); + api.addTodo(todo) + .then( () => { + console.log("Added TODO successfully"); + TodoActions.getAllTodos(); + }) + }, + + deleteTodo: (todo) => { + console.log("Deleting TODO"); + api.deleteTodo(todo.id) + .then( () => { + console.log("Deleted TODO successfully"); + TodoActions.getAllTodos(); + }) + }, + + markTodoDone: (todo) => { + console.log("Marking TODO as done"); + api.markTodoDone(todo) + .then( () => { + console.log("marked TODO as done successfully"); + TodoActions.getAllTodos(); + }) + }, + + markTodoUnDone: (todo) => { + console.log("Marking TODO as undone"); + api.markTodoUnDone(todo) + .then( () => { + console.log("marked TODO as undone successfully"); + TodoActions.getAllTodos(); + }) + }, + + getAllTodos: () => { + api.getTodos() + .then( (responseData) => { + var todos = responseData.todos; + console.log("new todos", todos); + TodoStore.setTodos(todos); + }) + } + +} + +module.exports = TodoActions; diff --git a/ep25-add-todo-using-flux/app/components/App.jsx b/ep25-add-todo-using-flux/app/components/App.jsx new file mode 100644 index 0000000..239e55d --- /dev/null +++ b/ep25-add-todo-using-flux/app/components/App.jsx @@ -0,0 +1,70 @@ +import React from 'react'; +import DisplayList from './DisplayList'; + +var rand = require('random-key'); +var api = require("../utils/api"); +var TodoStore = require("../stores/TodoStore"); +var TodoActions = require("../actions/TodoActions"); + +export default class App extends React.Component { + + constructor () { + super(); + this.state = { title: '', todos: [] }; + + this.getAllTodos(); + } + + componentDidMount () { + var storeIsTellingUsThatDataHasChanged = (todos) => { + console.log("Store is telling us that data has change"); + this.setState({todos: todos}); + } + TodoStore.addChangeListener(storeIsTellingUsThatDataHasChanged); + } + + getAllTodos () { + api.getTodos() + .then( (responseData) => this.setState({todos: responseData.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

    +
    + +
    + + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    ; + } +} diff --git a/ep25-add-todo-using-flux/app/components/DisplayItem.jsx b/ep25-add-todo-using-flux/app/components/DisplayItem.jsx new file mode 100644 index 0000000..47d9af2 --- /dev/null +++ b/ep25-add-todo-using-flux/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/ep25-add-todo-using-flux/app/components/DisplayList.jsx b/ep25-add-todo-using-flux/app/components/DisplayList.jsx new file mode 100644 index 0000000..3cd852e --- /dev/null +++ b/ep25-add-todo-using-flux/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/ep25-add-todo-using-flux/app/main.jsx b/ep25-add-todo-using-flux/app/main.jsx new file mode 100644 index 0000000..aee7cdf --- /dev/null +++ b/ep25-add-todo-using-flux/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/ep25-add-todo-using-flux/app/stores/TodoStore.js b/ep25-add-todo-using-flux/app/stores/TodoStore.js new file mode 100644 index 0000000..e957043 --- /dev/null +++ b/ep25-add-todo-using-flux/app/stores/TodoStore.js @@ -0,0 +1,22 @@ +var _todos = {}; +var _callback; + +var TodoStore = { + + setTodos: (todos) => { + _todos = todos; + console.log("TodoStore", TodoStore.getTodos()); + _callback(todos); + }, + + getTodos: () => { + return _todos; + }, + + addChangeListener: function (callback) { + console.log("registering callback for changelistener"); + _callback = callback; + } +} + +module.exports = TodoStore; diff --git a/ep25-add-todo-using-flux/app/stylesheets/main.css b/ep25-add-todo-using-flux/app/stylesheets/main.css new file mode 100644 index 0000000..ac2d81f --- /dev/null +++ b/ep25-add-todo-using-flux/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/ep25-add-todo-using-flux/app/utils/api.js b/ep25-add-todo-using-flux/app/utils/api.js new file mode 100644 index 0000000..1cfd44b --- /dev/null +++ b/ep25-add-todo-using-flux/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/ep25-add-todo-using-flux/app/utils/constants.js b/ep25-add-todo-using-flux/app/utils/constants.js new file mode 100644 index 0000000..2affc3c --- /dev/null +++ b/ep25-add-todo-using-flux/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/ep25-add-todo-using-flux/package.json b/ep25-add-todo-using-flux/package.json new file mode 100644 index 0000000..dd24820 --- /dev/null +++ b/ep25-add-todo-using-flux/package.json @@ -0,0 +1,31 @@ +{ + "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", + "whatwg-fetch": "^0.9.0" + } +} diff --git a/ep25-add-todo-using-flux/webpack.config.js b/ep25-add-todo-using-flux/webpack.config.js new file mode 100644 index 0000000..bb8e6c1 --- /dev/null +++ b/ep25-add-todo-using-flux/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/ep26-enhance-todo-store/README.md b/ep26-enhance-todo-store/README.md new file mode 100644 index 0000000..2712807 --- /dev/null +++ b/ep26-enhance-todo-store/README.md @@ -0,0 +1,8 @@ +#### Setting up the application + +``` +npm install +npm start +``` + +Visit http://localhost:8080 in browser. diff --git a/ep26-enhance-todo-store/app/actions/TodoActions.js b/ep26-enhance-todo-store/app/actions/TodoActions.js new file mode 100644 index 0000000..d15fb11 --- /dev/null +++ b/ep26-enhance-todo-store/app/actions/TodoActions.js @@ -0,0 +1,54 @@ +var api = require("../utils/api"); +var TodoStore = require("../stores/TodoStore"); + +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"); + TodoStore.deleteTodo(todo); + }) + }, + + markTodoDone: (todo) => { + console.log("Marking TODO as done"); + api.markTodoDone(todo) + .then( () => { + console.log("marked TODO as done successfully"); + TodoStore.markTodoDone(todo); + }) + }, + + markTodoUnDone: (todo) => { + console.log("Marking TODO as undone"); + api.markTodoUnDone(todo) + .then( () => { + console.log("marked TODO as undone successfully"); + TodoStore.markTodoUnDone(todo); + }) + }, + + getAllTodosAndUpdateStore: () => { + console.log("Performing getAllTodos"); + api.getTodos() + .then( (responseData) => { + var todos = responseData.todos; + console.log("new todos", todos); + TodoStore.setTodos(todos); + }) + } + +} + +module.exports = TodoActions; diff --git a/ep26-enhance-todo-store/app/components/App.jsx b/ep26-enhance-todo-store/app/components/App.jsx new file mode 100644 index 0000000..ee25a0b --- /dev/null +++ b/ep26-enhance-todo-store/app/components/App.jsx @@ -0,0 +1,75 @@ +import React from 'react'; +import DisplayList from './DisplayList'; + +var rand = require('random-key'); +var api = require("../utils/api"); +var TodoStore = require("../stores/TodoStore"); +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 = (todos) => { + console.log("Store is telling us that data has change"); + 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

    +
    + +
    + + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    ; + } +} diff --git a/ep26-enhance-todo-store/app/components/DisplayItem.jsx b/ep26-enhance-todo-store/app/components/DisplayItem.jsx new file mode 100644 index 0000000..47d9af2 --- /dev/null +++ b/ep26-enhance-todo-store/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/ep26-enhance-todo-store/app/components/DisplayList.jsx b/ep26-enhance-todo-store/app/components/DisplayList.jsx new file mode 100644 index 0000000..3cd852e --- /dev/null +++ b/ep26-enhance-todo-store/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/ep26-enhance-todo-store/app/main.jsx b/ep26-enhance-todo-store/app/main.jsx new file mode 100644 index 0000000..aee7cdf --- /dev/null +++ b/ep26-enhance-todo-store/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/ep26-enhance-todo-store/app/stores/TodoStore.js b/ep26-enhance-todo-store/app/stores/TodoStore.js new file mode 100644 index 0000000..4f992e6 --- /dev/null +++ b/ep26-enhance-todo-store/app/stores/TodoStore.js @@ -0,0 +1,48 @@ +var _todos = {}; +var _callback; + +var TodoStore = { + + deleteTodo: (todo) => { + var newTodos = _todos.filter( (t) => { + return t.id != todo.id + } ) + _todos = newTodos; + _callback(_todos); + }, + + markTodoDone: (todo) => { + var _todo = _todos.filter((t) => { + return t.id === todo.id; + })[0]; + + _todo.done = true; + _callback(_todos); + }, + + markTodoUnDone: (todo) => { + var _todo = _todos.filter((t) => { + return t.id === todo.id; + })[0]; + + _todo.done = false; + _callback(_todos); + }, + + setTodos: (todos) => { + _todos = todos; + console.log("TodoStore", TodoStore.getTodos()); + _callback(todos); + }, + + getTodos: () => { + return _todos; + }, + + addChangeListener: function (callback) { + console.log("registering callback for changelistener"); + _callback = callback; + } +} + +module.exports = TodoStore; diff --git a/ep26-enhance-todo-store/app/stylesheets/main.css b/ep26-enhance-todo-store/app/stylesheets/main.css new file mode 100644 index 0000000..ac2d81f --- /dev/null +++ b/ep26-enhance-todo-store/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/ep26-enhance-todo-store/app/utils/api.js b/ep26-enhance-todo-store/app/utils/api.js new file mode 100644 index 0000000..1cfd44b --- /dev/null +++ b/ep26-enhance-todo-store/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/ep26-enhance-todo-store/app/utils/constants.js b/ep26-enhance-todo-store/app/utils/constants.js new file mode 100644 index 0000000..2affc3c --- /dev/null +++ b/ep26-enhance-todo-store/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/ep26-enhance-todo-store/package.json b/ep26-enhance-todo-store/package.json new file mode 100644 index 0000000..dd24820 --- /dev/null +++ b/ep26-enhance-todo-store/package.json @@ -0,0 +1,31 @@ +{ + "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", + "whatwg-fetch": "^0.9.0" + } +} diff --git a/ep26-enhance-todo-store/webpack.config.js b/ep26-enhance-todo-store/webpack.config.js new file mode 100644 index 0000000..bb8e6c1 --- /dev/null +++ b/ep26-enhance-todo-store/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/ep27-use-dispatcher/README.md b/ep27-use-dispatcher/README.md new file mode 100644 index 0000000..2712807 --- /dev/null +++ b/ep27-use-dispatcher/README.md @@ -0,0 +1,8 @@ +#### Setting up the application + +``` +npm install +npm start +``` + +Visit http://localhost:8080 in browser. diff --git a/ep27-use-dispatcher/app/actions/TodoActions.js b/ep27-use-dispatcher/app/actions/TodoActions.js new file mode 100644 index 0000000..eaa605f --- /dev/null +++ b/ep27-use-dispatcher/app/actions/TodoActions.js @@ -0,0 +1,60 @@ +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( () => { + console.log("Added TODO successfully"); + TodoActions.getAllTodosAndUpdateStore(); + }) + }, + + deleteTodo: (todo) => { + console.log("Deleting TODO"); + api.deleteTodo(todo.id) + .then( () => { + console.log("Deleted TODO successfully"); + TodoStore.deleteTodo(todo); + }) + }, + + markTodoDone: (todo) => { + console.log("Marking TODO as done"); + api.markTodoDone(todo) + .then( () => { + console.log("marked TODO as done successfully"); + //TodoStore.markTodoDone(todo); + 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"); + TodoStore.markTodoUnDone(todo); + }) + }, + + getAllTodosAndUpdateStore: () => { + console.log("Performing getAllTodos"); + api.getTodos() + .then( (responseData) => { + var todos = responseData.todos; + console.log("new todos", todos); + TodoStore.setTodos(todos); + }) + } + +} + +module.exports = TodoActions; diff --git a/ep27-use-dispatcher/app/components/App.jsx b/ep27-use-dispatcher/app/components/App.jsx new file mode 100644 index 0000000..1dff5bb --- /dev/null +++ b/ep27-use-dispatcher/app/components/App.jsx @@ -0,0 +1,74 @@ +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 = (todos) => { + console.log("Store is telling us that data has change"); + 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

    +
    + +
    + + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    ; + } +} diff --git a/ep27-use-dispatcher/app/components/DisplayItem.jsx b/ep27-use-dispatcher/app/components/DisplayItem.jsx new file mode 100644 index 0000000..47d9af2 --- /dev/null +++ b/ep27-use-dispatcher/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/ep27-use-dispatcher/app/components/DisplayList.jsx b/ep27-use-dispatcher/app/components/DisplayList.jsx new file mode 100644 index 0000000..3cd852e --- /dev/null +++ b/ep27-use-dispatcher/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/ep27-use-dispatcher/app/dispatcher/AppDispatcher.js b/ep27-use-dispatcher/app/dispatcher/AppDispatcher.js new file mode 100644 index 0000000..5231a4e --- /dev/null +++ b/ep27-use-dispatcher/app/dispatcher/AppDispatcher.js @@ -0,0 +1,3 @@ +var Dispatcher = require('flux').Dispatcher; + +module.exports = new Dispatcher(); diff --git a/ep27-use-dispatcher/app/main.jsx b/ep27-use-dispatcher/app/main.jsx new file mode 100644 index 0000000..aee7cdf --- /dev/null +++ b/ep27-use-dispatcher/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/ep27-use-dispatcher/app/stores/TodoStore.js b/ep27-use-dispatcher/app/stores/TodoStore.js new file mode 100644 index 0000000..083b498 --- /dev/null +++ b/ep27-use-dispatcher/app/stores/TodoStore.js @@ -0,0 +1,61 @@ +var AppDispatcher = require('../dispatcher/AppDispatcher'); + +AppDispatcher.register(function(action) { + + switch(action.actionType) { + case 'TODO_DONE': + console.log("Handling TODO_DONE using dispatcher in store"); + TodoStore.markTodoDone(action.todo); + break; + } + +}); + +var _todos = {}; +var _callback; + +var TodoStore = { + + deleteTodo: (todo) => { + var newTodos = _todos.filter( (t) => { + return t.id != todo.id + } ) + _todos = newTodos; + _callback(_todos); + }, + + markTodoDone: (todo) => { + var _todo = _todos.filter((t) => { + return t.id === todo.id; + })[0]; + + _todo.done = true; + _callback(_todos); + }, + + markTodoUnDone: (todo) => { + var _todo = _todos.filter((t) => { + return t.id === todo.id; + })[0]; + + _todo.done = false; + _callback(_todos); + }, + + setTodos: (todos) => { + _todos = todos; + console.log("TodoStore", TodoStore.getTodos()); + _callback(todos); + }, + + getTodos: () => { + return _todos; + }, + + addChangeListener: function (callback) { + console.log("registering callback for changelistener"); + _callback = callback; + } +} + +module.exports = TodoStore; diff --git a/ep27-use-dispatcher/app/stylesheets/main.css b/ep27-use-dispatcher/app/stylesheets/main.css new file mode 100644 index 0000000..ac2d81f --- /dev/null +++ b/ep27-use-dispatcher/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/ep27-use-dispatcher/app/utils/api.js b/ep27-use-dispatcher/app/utils/api.js new file mode 100644 index 0000000..1cfd44b --- /dev/null +++ b/ep27-use-dispatcher/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/ep27-use-dispatcher/app/utils/constants.js b/ep27-use-dispatcher/app/utils/constants.js new file mode 100644 index 0000000..2affc3c --- /dev/null +++ b/ep27-use-dispatcher/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/ep27-use-dispatcher/package.json b/ep27-use-dispatcher/package.json new file mode 100644 index 0000000..b0d9658 --- /dev/null +++ b/ep27-use-dispatcher/package.json @@ -0,0 +1,32 @@ +{ + "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", + "random-key": "^0.3.2", + "react": "^0.13.3", + "whatwg-fetch": "^0.9.0" + } +} diff --git a/ep27-use-dispatcher/webpack.config.js b/ep27-use-dispatcher/webpack.config.js new file mode 100644 index 0000000..bb8e6c1 --- /dev/null +++ b/ep27-use-dispatcher/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/ep28-more-dispatcher-usage/README.md b/ep28-more-dispatcher-usage/README.md new file mode 100644 index 0000000..2712807 --- /dev/null +++ b/ep28-more-dispatcher-usage/README.md @@ -0,0 +1,8 @@ +#### Setting up the application + +``` +npm install +npm start +``` + +Visit http://localhost:8080 in browser. diff --git a/ep28-more-dispatcher-usage/app/actions/TodoActions.js b/ep28-more-dispatcher-usage/app/actions/TodoActions.js new file mode 100644 index 0000000..b6f2666 --- /dev/null +++ b/ep28-more-dispatcher-usage/app/actions/TodoActions.js @@ -0,0 +1,59 @@ +var api = require("../utils/api"); +var TodoStore = require("../stores/TodoStore"); +var AppDispatcher = require('../dispatcher/AppDispatcher'); +var Constants = require("../utils/constants"); + +var TodoActions = { + + addTodo: (todo) => { + api.addTodo(todo) + .then( () => { + TodoActions.getAllTodosAndUpdateStore(); + }) + }, + + deleteTodo: (todo) => { + api.deleteTodo(todo.id) + .then( () => { + AppDispatcher.dispatch({ + actionType: Constants.TODO_DELETE, + todo: todo + }); + }) + }, + + markTodoDone: (todo) => { + api.markTodoDone(todo) + .then( () => { + AppDispatcher.dispatch({ + actionType: Constants.TODO_DONE, + todo: todo + }); + + }) + }, + + markTodoUnDone: (todo) => { + api.markTodoUnDone(todo) + .then( () => { + AppDispatcher.dispatch({ + actionType: Constants.TODO_UNDONE, + todo: todo + }); + }) + }, + + getAllTodosAndUpdateStore: () => { + api.getTodos() + .then( (responseData) => { + var todos = responseData.todos; + TodoStore.setTodos(todos); + AppDispatcher.dispatch({ + actionType: Constants.TODO_ADD + }); + }) + } + +} + +module.exports = TodoActions; diff --git a/ep28-more-dispatcher-usage/app/components/App.jsx b/ep28-more-dispatcher-usage/app/components/App.jsx new file mode 100644 index 0000000..b9150d2 --- /dev/null +++ b/ep28-more-dispatcher-usage/app/components/App.jsx @@ -0,0 +1,73 @@ +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 = (todos) => { + console.log("Store is telling us that data has change"); + 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

    +
    + +
    + + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    ; + } +} diff --git a/ep28-more-dispatcher-usage/app/components/DisplayItem.jsx b/ep28-more-dispatcher-usage/app/components/DisplayItem.jsx new file mode 100644 index 0000000..47d9af2 --- /dev/null +++ b/ep28-more-dispatcher-usage/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/ep28-more-dispatcher-usage/app/components/DisplayList.jsx b/ep28-more-dispatcher-usage/app/components/DisplayList.jsx new file mode 100644 index 0000000..a254b58 --- /dev/null +++ b/ep28-more-dispatcher-usage/app/components/DisplayList.jsx @@ -0,0 +1,22 @@ +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/ep28-more-dispatcher-usage/app/dispatcher/AppDispatcher.js b/ep28-more-dispatcher-usage/app/dispatcher/AppDispatcher.js new file mode 100644 index 0000000..5231a4e --- /dev/null +++ b/ep28-more-dispatcher-usage/app/dispatcher/AppDispatcher.js @@ -0,0 +1,3 @@ +var Dispatcher = require('flux').Dispatcher; + +module.exports = new Dispatcher(); diff --git a/ep28-more-dispatcher-usage/app/main.jsx b/ep28-more-dispatcher-usage/app/main.jsx new file mode 100644 index 0000000..aee7cdf --- /dev/null +++ b/ep28-more-dispatcher-usage/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/ep28-more-dispatcher-usage/app/stores/TodoStore.js b/ep28-more-dispatcher-usage/app/stores/TodoStore.js new file mode 100644 index 0000000..f2b31f4 --- /dev/null +++ b/ep28-more-dispatcher-usage/app/stores/TodoStore.js @@ -0,0 +1,74 @@ +var AppDispatcher = require('../dispatcher/AppDispatcher'); +var Constants = require("../utils/constants"); + +AppDispatcher.register(function(action) { + + switch(action.actionType) { + case Constants.TODO_DONE: + TodoStore.markTodoDone(action.todo); + break; + + case Constants.TODO_UNDONE: + TodoStore.markTodoUnDone(action.todo); + break; + + case Constants.TODO_DELETE: + TodoStore.deleteTodo(action.todo); + break; + + case Constants.TODO_ADD: + TodoStore.getTodos(); + break; + } + +}); + +var _todos = {}; +var _callback; + +var TodoStore = { + + deleteTodo: (todo) => { + var newTodos = _todos.filter( (t) => { + return t.id != todo.id + } ) + _todos = newTodos; + _callback(_todos); + }, + + markTodoDone: (todo) => { + var _todo = _todos.filter((t) => { + return t.id === todo.id; + })[0]; + + _todo.done = true; + _callback(_todos); + }, + + markTodoUnDone: (todo) => { + var _todo = _todos.filter((t) => { + return t.id === todo.id; + })[0]; + + _todo.done = false; + _callback(_todos); + }, + + setTodos: (todos) => { + _todos = todos; + console.log("TodoStore", TodoStore.getTodos()); + _callback(todos); + }, + + getTodos: () => { + return _todos; + _callback(todos); + }, + + addChangeListener: function (callback) { + console.log("registering callback for changelistener"); + _callback = callback; + } +} + +module.exports = TodoStore; diff --git a/ep28-more-dispatcher-usage/app/stylesheets/main.css b/ep28-more-dispatcher-usage/app/stylesheets/main.css new file mode 100644 index 0000000..ac2d81f --- /dev/null +++ b/ep28-more-dispatcher-usage/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/ep28-more-dispatcher-usage/app/utils/api.js b/ep28-more-dispatcher-usage/app/utils/api.js new file mode 100644 index 0000000..1cfd44b --- /dev/null +++ b/ep28-more-dispatcher-usage/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/ep28-more-dispatcher-usage/app/utils/constants.js b/ep28-more-dispatcher-usage/app/utils/constants.js new file mode 100644 index 0000000..3025eba --- /dev/null +++ b/ep28-more-dispatcher-usage/app/utils/constants.js @@ -0,0 +1,10 @@ +var Constants = { + BASE_URL: "http://lrjis-api-production.herokuapp.com/api/v1/", + 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/ep28-more-dispatcher-usage/package.json b/ep28-more-dispatcher-usage/package.json new file mode 100644 index 0000000..b0d9658 --- /dev/null +++ b/ep28-more-dispatcher-usage/package.json @@ -0,0 +1,32 @@ +{ + "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", + "random-key": "^0.3.2", + "react": "^0.13.3", + "whatwg-fetch": "^0.9.0" + } +} diff --git a/ep28-more-dispatcher-usage/webpack.config.js b/ep28-more-dispatcher-usage/webpack.config.js new file mode 100644 index 0000000..bb8e6c1 --- /dev/null +++ b/ep28-more-dispatcher-usage/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/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

    +
    + +
    + + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    ; + } +} 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
    + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    + } +} 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
    + + +
    + All: ({ this.state.todos.length }) | + Completed: ({ this.state.todos.filter((todo) => { return todo.done }).length }) | + Pending: ({ this.state.todos.filter((todo) => { return !todo.done }).length }) | + Clear Completed +
    +
    + } +} 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'), + }, + ], + }, + }); +}