diff --git a/Gruntfile.js b/Gruntfile.js index 5c03155..b9dc668 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,7 +1,7 @@ module.exports = function(grunt) { var options = { - port: 8080 + port: 8888 }; grunt.initConfig({ diff --git a/index.html b/index.html index a730828..28d9fb5 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ ReactJS + RefluxJS • TodoMVC - + @@ -22,13 +22,17 @@ - - - - - + + + + + - - + + + + + + diff --git a/js/TodoApp.jsx.js b/js/TodoApp.jsx.js new file mode 100644 index 0000000..49e0eb8 --- /dev/null +++ b/js/TodoApp.jsx.js @@ -0,0 +1,19 @@ +(function(React, ReactRouter, Reflux, TodoHeader, TodoFooter, todoListStore, global) { + // Renders the full application + // RouteHandler will always be TodoList, but with different 'showing' prop (all/completed/active) + global.TodoApp = React.createClass({ + // this will cause setState({list:updatedlist}) whenever the store does trigger(updatedlist) + mixins: [Reflux.connect(todoListStore,"list")], + + render: function() { + return ( +
+ + + +
+ ); + } + }); + +})(window.React, window.ReactRouter, window.Reflux,window.TodoHeader, window.TodoFooter, window.todoListStore, window); \ No newline at end of file diff --git a/js/TodoFooter.jsx.js b/js/TodoFooter.jsx.js new file mode 100644 index 0000000..5e0286e --- /dev/null +++ b/js/TodoFooter.jsx.js @@ -0,0 +1,35 @@ +(function(React, ReactRouter, TodoActions, global) { + // Renders the bottom item count, navigation bar and clearallcompleted button + // Used in TodoApp + global.TodoFooter = React.createClass({ + propTypes: { + list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + }, + render: function() { + var nbrcompleted = _.filter(this.props.list, "isComplete").length, + nbrtotal = this.props.list.length, + nbrincomplete = nbrtotal-nbrcompleted, + clearButtonClass = React.addons.classSet({hidden: nbrcompleted < 1}), + footerClass = React.addons.classSet({hidden: !nbrtotal }), + completedLabel = "Clear completed (" + nbrcompleted + ")", + itemsLeftLabel = nbrincomplete === 1 ? " item left" : " items left"; + return ( + + ); + } + }); +})(window.React, window.ReactRouter, window.TodoActions, window); \ No newline at end of file diff --git a/js/TodoHeader.jsx.js b/js/TodoHeader.jsx.js new file mode 100644 index 0000000..c42177e --- /dev/null +++ b/js/TodoHeader.jsx.js @@ -0,0 +1,24 @@ +(function(React, ReactRouter, Reflux, TodoActions, todoListStore, global) { + // Renders the headline and the form for creating new todos. + // Used in TodoApp + // Observe that the toogleall button is NOT rendered here, but in TodoMain (it is then moved up to the header with CSS) + global.TodoHeader = React.createClass({ + handleValueChange: function(evt) { + var text = evt.target.value; + if (evt.which === 13 && text) { // hit enter, create new item if field isn't empty + TodoActions.addItem(text); + evt.target.value = ''; + } else if (evt.which === 27) { // hit escape, clear without creating + evt.target.value = ''; + } + }, + render: function() { + return ( + + ); + } + }); +})(window.React, window.ReactRouter, window.Reflux, window.TodoActions, window.todoListStore, window); diff --git a/js/TodoItem.jsx.js b/js/TodoItem.jsx.js new file mode 100644 index 0000000..3997c52 --- /dev/null +++ b/js/TodoItem.jsx.js @@ -0,0 +1,79 @@ +(function(React, TodoActions, global) { + // Renders a single Todo item in the list + // Used in TodoMain + global.TodoItem = React.createClass({ + propTypes: { + label: React.PropTypes.string.isRequired, + isComplete: React.PropTypes.bool.isRequired, + id: React.PropTypes.number + }, + mixins: [React.addons.LinkedStateMixin], // exposes this.linkState used in render + getInitialState: function() { + return {}; + }, + handleToggle: function(evt) { + TodoActions.toggleItem(this.props.id); + }, + handleEditStart: function(evt) { + evt.preventDefault(); + // because of linkState call in render, field will get value from this.state.editValue + this.setState({ + isEditing: true, + editValue: this.props.label + }, function() { + this.refs.editInput.getDOMNode().focus(); + }); + }, + handleCompleted: function(evt){ + evt.preventDefault(); + // because of linkState call in render, field will get value from this.state.editValue + setTimeout(function () { + if(!this.state.isEditing){ + this.refs.toggleCompleted.getDOMNode().click(); + } + }.bind(this), 200); + }, + handleValueChange: function(evt) { + var text = this.state.editValue; // because of the linkState call in render, this is the contents of the field + // we pressed enter, if text isn't empty we blur the field which will cause a save + if (evt.which === 13 && text) { + this.refs.editInput.getDOMNode().blur(); + } + // pressed escape. set editing to false before blurring so we won't save + else if (evt.which === 27) { + this.setState({ isEditing: false },function(){ + this.refs.editInput.getDOMNode().blur(); + }); + } + }, + handleBlur: function() { + var text = this.state.editValue; // because of the linkState call in render, this is the contents of the field + // unless we're not editing (escape was pressed) or text is empty, save! + if (this.state.isEditing && text) { + TodoActions.editItem(this.props.id, text); + } + // whatever the outcome, if we left the field we're not editing anymore + this.setState({isEditing:false}); + }, + handleDestroy: function() { + TodoActions.removeItem(this.props.id); + }, + render: function() { + var classes = React.addons.classSet({ + 'completed': this.props.isComplete, + 'editing': this.state.isEditing + }); + return ( +
  • +
    + + + +
    + +
  • + ); + } + }); +})(window.React, window.TodoActions, window); + diff --git a/js/TodoList.jsx.js b/js/TodoList.jsx.js new file mode 100644 index 0000000..6649e08 --- /dev/null +++ b/js/TodoList.jsx.js @@ -0,0 +1,44 @@ +/** @jsx React.DOM */ + +(function(React, ReactRouter, TodoItem, TodoActions, global) { + + // Renders the todo list as well as the toggle all button + // Used in TodoApp + global.TodoList = React.createClass({ + mixins: [ ReactRouter.State ], + propTypes: { + list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + }, + toggleAll: function(evt) { + TodoActions.toggleAllItems(evt.target.checked); + }, + render: function() { + var filteredList; + switch(this.getPath()){ + case '/completed': + filteredList = _.filter(this.props.list,function(item){ return item.isComplete; }); + break; + case '/active': + filteredList = _.filter(this.props.list,function(item){ return !item.isComplete; }); + break; + default: + filteredList = this.props.list; + } + var classes = React.addons.classSet({ + "hidden": this.props.list.length < 1 + }); + return ( +
    + + + +
    + ); + } + }); + +})(window.React, window.ReactRouter, window.TodoItem, window.TodoActions, window); diff --git a/js/components.jsx.js b/js/components.jsx.js deleted file mode 100644 index ca0d4b0..0000000 --- a/js/components.jsx.js +++ /dev/null @@ -1,198 +0,0 @@ -/** @jsx React.DOM */ - -(function(React, ReactRouter, Reflux, TodoActions, todoListStore, global) { - - // Renders a single Todo item in the list - // Used in TodoMain - var TodoItem = React.createClass({ - propTypes: { - label: React.PropTypes.string.isRequired, - isComplete: React.PropTypes.bool.isRequired, - id: React.PropTypes.number - }, - mixins: [React.addons.LinkedStateMixin], // exposes this.linkState used in render - getInitialState: function() { - return {}; - }, - handleToggle: function(evt) { - TodoActions.toggleItem(this.props.id); - }, - handleEditStart: function(evt) { - evt.preventDefault(); - // because of linkState call in render, field will get value from this.state.editValue - this.setState({ - isEditing: true, - editValue: this.props.label - }, function() { - this.refs.editInput.getDOMNode().focus(); - }); - }, - handleValueChange: function(evt) { - var text = this.state.editValue; // because of the linkState call in render, this is the contents of the field - // we pressed enter, if text isn't empty we blur the field which will cause a save - if (evt.which === 13 && text) { - this.refs.editInput.getDOMNode().blur(); - } - // pressed escape. set editing to false before blurring so we won't save - else if (evt.which === 27) { - this.setState({ isEditing: false },function(){ - this.refs.editInput.getDOMNode().blur(); - }); - } - }, - handleBlur: function() { - var text = this.state.editValue; // because of the linkState call in render, this is the contents of the field - // unless we're not editing (escape was pressed) or text is empty, save! - if (this.state.isEditing && text) { - TodoActions.editItem(this.props.id, text); - } - // whatever the outcome, if we left the field we're not editing anymore - this.setState({isEditing:false}); - }, - handleDestroy: function() { - TodoActions.removeItem(this.props.id); - }, - render: function() { - var classes = React.addons.classSet({ - 'completed': this.props.isComplete, - 'editing': this.state.isEditing - }); - return ( -
  • -
    - - - -
    - -
  • - ); - } - }); - - // Renders the todo list as well as the toggle all button - // Used in TodoApp - var TodoMain = React.createClass({ - mixins: [ ReactRouter.State ], - propTypes: { - list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, - }, - toggleAll: function(evt) { - TodoActions.toggleAllItems(evt.target.checked); - }, - render: function() { - var filteredList; - switch(this.getPath()){ - case '/completed': - filteredList = _.filter(this.props.list,function(item){ return item.isComplete; }); - break; - case '/active': - filteredList = _.filter(this.props.list,function(item){ return !item.isComplete; }); - break; - default: - filteredList = this.props.list; - } - var classes = React.addons.classSet({ - "hidden": this.props.list.length < 1 - }); - return ( -
    - - - -
    - ); - } - }); - - // Renders the headline and the form for creating new todos. - // Used in TodoApp - // Observe that the toogleall button is NOT rendered here, but in TodoMain (it is then moved up to the header with CSS) - var TodoHeader = React.createClass({ - handleValueChange: function(evt) { - var text = evt.target.value; - if (evt.which === 13 && text) { // hit enter, create new item if field isn't empty - TodoActions.addItem(text); - evt.target.value = ''; - } else if (evt.which === 27) { // hit escape, clear without creating - evt.target.value = ''; - } - }, - render: function() { - return ( - - ); - } - }); - - // Renders the bottom item count, navigation bar and clearallcompleted button - // Used in TodoApp - var TodoFooter = React.createClass({ - propTypes: { - list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, - }, - render: function() { - var nbrcompleted = _.filter(this.props.list, "isComplete").length, - nbrtotal = this.props.list.length, - nbrincomplete = nbrtotal-nbrcompleted, - clearButtonClass = React.addons.classSet({hidden: nbrcompleted < 1}), - footerClass = React.addons.classSet({hidden: !nbrtotal }), - completedLabel = "Clear completed (" + nbrcompleted + ")", - itemsLeftLabel = nbrincomplete === 1 ? " item left" : " items left"; - return ( - - ); - } - }); - - // Renders the full application - // RouteHandler will always be TodoMain, but with different 'showing' prop (all/completed/active) - var TodoApp = React.createClass({ - // this will cause setState({list:updatedlist}) whenever the store does trigger(updatedlist) - mixins: [Reflux.connect(todoListStore,"list")], - - render: function() { - return ( -
    - - - -
    - ); - } - }); - - var routes = ( - - - - - - ); - - ReactRouter.run(routes, function(Handler) { - React.render(, document.getElementById('todoapp')); - }); - -})(window.React, window.ReactRouter, window.Reflux, window.TodoActions, window.todoListStore, window); diff --git a/js/router.js b/js/router.js new file mode 100644 index 0000000..a83b0a9 --- /dev/null +++ b/js/router.js @@ -0,0 +1,12 @@ +(function(React, ReactRouter, TodoApp,TodoList, global) { + var routes = ( + + + + + + ); + ReactRouter.run(routes, function(Handler) { + React.render(, document.getElementById('todoapp')); + }); +})(window.React, window.ReactRouter,window.TodoApp, window.TodoList, window); \ No newline at end of file diff --git a/testMixin.html b/testMixin.html new file mode 100644 index 0000000..49653f6 --- /dev/null +++ b/testMixin.html @@ -0,0 +1,62 @@ + + + + + + Mixins Example + + + + + +
    + +
    + + + + + + + + + +