Repository: spoike/refluxjs-todo Branch: master Commit: ce32dfaebb58 Files: 12 Total size: 16.9 KB Directory structure: gitextract_fnrg5n3l/ ├── .editorconfig ├── .gitignore ├── Gruntfile.js ├── bower.json ├── css/ │ └── app.css ├── index.html ├── js/ │ ├── actions.js │ ├── components.jsx.js │ └── store.js ├── package.json ├── readme.md └── readme_template.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] end_of_line = lf insert_final_newline = true indent_style = space trim_trailing_whitespace = true charset = utf-8 [*.js] indent_size = 4 [{package.json,.travis.yml}] indent_size = 2 ================================================ FILE: .gitignore ================================================ # Node.JS specifics lib-cov *.seed *.log *.csv *.dat *.out *.pid *.gz pids logs results npm-debug.log node_modules # MacOSX specifics .DS_Store # Build artifacts tmp bower_components components ================================================ FILE: Gruntfile.js ================================================ module.exports = function(grunt) { var options = { port: 8080 }; grunt.initConfig({ options: options, pkg: grunt.file.readJSON('package.json'), connect: { server: { options: { port: options.port, base: '.' } } }, open: { server: { path: 'http://localhost:<%= options.port %>/' } }, watch: { } }); require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); grunt.registerTask('default', ['connect', 'open', 'watch']); }; ================================================ FILE: bower.json ================================================ { "name": "reflux-todo", "version": "0.0.1", "dependencies": { "todomvc-app-css": "~1.0.1", "reflux": "~0.2.4", "react": "~0.12.2", "lodash": "~3.0.1", "react-router": "~0.11.6" } } ================================================ FILE: css/app.css ================================================ /* base.css overrides */ ================================================ FILE: index.html ================================================ ReactJS + RefluxJS • TodoMVC
================================================ FILE: js/actions.js ================================================ (function(Reflux, global) { 'use strict'; // Each action is like an event channel for one specific event. Actions are called by components. // The store is listening to all actions, and the components in turn are listening to the store. // Thus the flow is: User interaction -> component calls action -> store reacts and triggers -> components update global.TodoActions = Reflux.createActions([ "toggleItem", // called by button in TodoItem "toggleAllItems", // called by button in TodoMain (even though you'd think TodoHeader) "addItem", // called by hitting enter in field in TodoHeader "removeItem", // called by button in TodoItem "clearCompleted", // called by button in TodoFooter "editItem" // called by finishing edit in TodoItem ]); })(window.Reflux, window); ================================================ FILE: js/components.jsx.js ================================================ /** @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); ================================================ FILE: js/store.js ================================================ (function(Reflux, TodoActions, global) { 'use strict'; // some variables and helpers for our fake database stuff var todoCounter = 0, localStorageKey = "todos"; function getItemByKey(list,itemKey){ return _.find(list, function(item) { return item.key === itemKey; }); } global.todoListStore = Reflux.createStore({ // this will set up listeners to all publishers in TodoActions, using onKeyname (or keyname) as callbacks listenables: [TodoActions], onEditItem: function(itemKey, newLabel) { var foundItem = getItemByKey(this.list,itemKey); if (!foundItem) { return; } foundItem.label = newLabel; this.updateList(this.list); }, onAddItem: function(label) { this.updateList([{ key: todoCounter++, created: new Date(), isComplete: false, label: label }].concat(this.list)); }, onRemoveItem: function(itemKey) { this.updateList(_.filter(this.list,function(item){ return item.key!==itemKey; })); }, onToggleItem: function(itemKey) { var foundItem = getItemByKey(this.list,itemKey); if (foundItem) { foundItem.isComplete = !foundItem.isComplete; this.updateList(this.list); } }, onToggleAllItems: function(checked) { this.updateList(_.map(this.list, function(item) { item.isComplete = checked; return item; })); }, onClearCompleted: function() { this.updateList(_.filter(this.list, function(item) { return !item.isComplete; })); }, // called whenever we change a list. normally this would mean a database API call updateList: function(list){ localStorage.setItem(localStorageKey, JSON.stringify(list)); // if we used a real database, we would likely do the below in a callback this.list = list; this.trigger(list); // sends the updated list to all listening components (TodoApp) }, // this will be called by all listening components as they register their listeners getInitialState: function() { var loadedList = localStorage.getItem(localStorageKey); if (!loadedList) { // If no list is in localstorage, start out with a default one this.list = [{ key: todoCounter++, created: new Date(), isComplete: false, label: 'Rule the web' }]; } else { this.list = _.map(JSON.parse(loadedList), function(item) { // just resetting the key property for each todo item item.key = todoCounter++; return item; }); } return this.list; } }); })(window.Reflux, window.TodoActions, window); ================================================ FILE: package.json ================================================ { "name": "reflux-todo", "version": "0.0.1", "description": "Todo example for the reflux project", "main": "index.js", "scripts": { "prepublish": "bower install", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "MIT", "devDependencies": { "grunt": "^0.4.5", "grunt-contrib-connect": "^0.8.0", "grunt-contrib-watch": "^0.6.1", "grunt-open": "^0.2.3", "matchdep": "^0.3.0" } } ================================================ FILE: readme.md ================================================ # ReactJS w. RefluxJS TodoMVC Example > A simple library for uni-directional dataflow application architecture inspired by ReactJS [Flux](http://facebook.github.io/react/blog/2014/05/06/flux.html) > _[RefluxJS](https://github.com/spoike/refluxjs)_ ## Implementation TODO ## Running Install dependencies with bower and npm. You'll first need to have [bower](http://bower.io/) and [npm](npmjs.org) installed to do so. Then run the following: ``` bower install && npm install ``` This project comes with a grunt task to runs a [`connect`](https://github.com/gruntjs/grunt-contrib-connect) web server and opens up the web browser for you. Just run: ``` grunt ``` ## Credit This TodoMVC application was created by [Mikael Brassman](https://github.com/spoike/refluxjs). ================================================ FILE: readme_template.md ================================================ # Template • [TodoMVC](http://todomvc.com) ## Getting Started Read the [App Specification](https://github.com/tastejs/todomvc/blob/master/app-spec.md) before touching the template. ## Need help? Feel free to contact [Sindre](https://github.com/sindresorhus) or [Pascal](https://github.com/passy) if you have any questions or need help with the template. ## Credit Created by [Sindre Sorhus](http://sindresorhus.com)