├── .client
├── .babelrc
├── .gitignore
├── actions
│ └── TodoActions.js
├── components
│ ├── Footer.js
│ ├── Header.js
│ ├── MainSection.js
│ ├── TodoApp.js
│ ├── TodoItem.js
│ └── TodoTextInput.js
├── constants
│ ├── ActionTypes.js
│ └── TodoFilters.js
├── containers
│ ├── App.js
│ └── Root.js
├── index.js
├── package.json
├── reducers
│ ├── index.js
│ └── todos.js
├── server.js
├── stores
│ ├── index.js
│ └── todos.js
└── webpack.config.js
├── .gitignore
├── .meteor
├── .finished-upgraders
├── .gitignore
├── .id
├── packages
├── platforms
├── release
└── versions
├── README.md
├── client
└── .git-keep
├── index.html
├── lib
└── models
│ └── todo.js
└── makefile
/.client/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0
3 | }
4 |
--------------------------------------------------------------------------------
/.client/.gitignore:
--------------------------------------------------------------------------------
1 | .meteor/local
2 | .meteor/meteorite
3 |
4 |
5 | .DS_Store
6 | .AppleDouble
7 | ._*
8 | .Trashes
9 | .Spotlight-V100
10 |
11 |
12 | .idea
13 |
14 |
15 | Thumb.db
16 | ehthumb.db
17 | Desktop.ini
18 | $RECYCLE.BIN/
19 |
20 | *.cab
21 | *.msi
22 | *.msm
23 | *.msp
24 |
25 |
26 | *~
27 | .directory
28 | node_modules
29 | client/bundle.js
--------------------------------------------------------------------------------
/.client/actions/TodoActions.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/ActionTypes';
2 |
3 | export function addTodo(text) {
4 | return {
5 | type: types.ADD_TODO,
6 | text
7 | };
8 | }
9 |
10 | export function deleteTodo(id) {
11 | return {
12 | type: types.DELETE_TODO,
13 | id
14 | };
15 | }
16 |
17 | export function editTodo(id, text) {
18 | return {
19 | type: types.EDIT_TODO,
20 | id,
21 | text
22 | };
23 | }
24 |
25 | export function markTodo(id) {
26 | return {
27 | type: types.MARK_TODO,
28 | id
29 | };
30 | }
31 |
32 | export function markAll() {
33 | return {
34 | type: types.MARK_ALL
35 | };
36 | }
37 |
38 | export function clearMarked() {
39 | return {
40 | type: types.CLEAR_MARKED
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/.client/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react';
2 | import classnames from 'classnames';
3 | import { SHOW_ALL, SHOW_MARKED, SHOW_UNMARKED } from '../constants/TodoFilters';
4 |
5 | const FILTER_TITLES = {
6 | [SHOW_ALL]: 'All',
7 | [SHOW_UNMARKED]: 'Active',
8 | [SHOW_MARKED]: 'Completed'
9 | };
10 |
11 | export default class Footer extends Component {
12 | static propTypes = {
13 | markedCount: PropTypes.number.isRequired,
14 | unmarkedCount: PropTypes.number.isRequired,
15 | filter: PropTypes.string.isRequired,
16 | onClearMarked: PropTypes.func.isRequired,
17 | onShow: PropTypes.func.isRequired
18 | }
19 |
20 | render() {
21 | return (
22 |
33 | );
34 | }
35 |
36 | renderTodoCount() {
37 | const { unmarkedCount } = this.props;
38 | const itemWord = unmarkedCount === 1 ? 'item' : 'items';
39 |
40 | return (
41 |
42 | {unmarkedCount || 'No'} {itemWord} left
43 |
44 | );
45 | }
46 |
47 | renderFilterLink(filter) {
48 | const title = FILTER_TITLES[filter];
49 | const { filter: selectedFilter, onShow } = this.props;
50 |
51 | return (
52 | onShow(filter)}>
55 | {title}
56 |
57 | );
58 | }
59 |
60 | renderClearButton() {
61 | const { markedCount, onClearMarked } = this.props;
62 | if (markedCount > 0) {
63 | return (
64 |
68 | );
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/.client/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { PropTypes, Component } from 'react';
2 | import TodoTextInput from './TodoTextInput';
3 |
4 | export default class Header extends Component {
5 | static propTypes = {
6 | addTodo: PropTypes.func.isRequired
7 | };
8 |
9 | handleSave(text) {
10 | if (text.length !== 0) {
11 | this.props.addTodo(text);
12 | }
13 | }
14 |
15 | render() {
16 | return (
17 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.client/components/MainSection.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import TodoItem from './TodoItem';
3 | import Footer from './Footer';
4 | import { SHOW_ALL, SHOW_MARKED, SHOW_UNMARKED } from '../constants/TodoFilters';
5 |
6 | const TODO_FILTERS = {
7 | [SHOW_ALL]: () => true,
8 | [SHOW_UNMARKED]: todo => !todo.marked,
9 | [SHOW_MARKED]: todo => todo.marked
10 | };
11 |
12 | export default class MainSection extends Component {
13 | static propTypes = {
14 | todos: PropTypes.array.isRequired,
15 | actions: PropTypes.object.isRequired
16 | };
17 |
18 | constructor(props, context) {
19 | super(props, context);
20 | this.state = { filter: SHOW_ALL };
21 | }
22 |
23 | handleClearMarked() {
24 | const atLeastOneMarked = this.props.todos.some(todo => todo.marked);
25 | if (atLeastOneMarked) {
26 | this.props.actions.clearMarked();
27 | }
28 | }
29 |
30 | handleShow(filter) {
31 | this.setState({ filter });
32 | }
33 |
34 | render() {
35 | const { todos, actions } = this.props;
36 | const { filter } = this.state;
37 |
38 | const filteredTodos = todos.filter(TODO_FILTERS[filter]);
39 | const markedCount = todos.reduce((count, todo) =>
40 | todo.marked ? count + 1 : count,
41 | 0
42 | );
43 |
44 | return (
45 |
46 | {this.renderToggleAll(markedCount)}
47 |
48 | {filteredTodos.map(todo =>
49 |
50 | )}
51 |
52 | {this.renderFooter(markedCount)}
53 |
54 | );
55 | }
56 |
57 | renderToggleAll(markedCount) {
58 | const { todos, actions } = this.props;
59 | if (todos.length > 0) {
60 | return (
61 |
65 | );
66 | }
67 | }
68 |
69 | renderFooter(markedCount) {
70 | const { todos } = this.props;
71 | const { filter } = this.state;
72 | const unmarkedCount = todos.length - markedCount;
73 |
74 | if (todos.length) {
75 | return (
76 |
81 | );
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/.client/components/TodoApp.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import Header from './Header';
3 | import MainSection from './MainSection';
4 |
5 |
6 | export default class TodoApp extends Component {
7 | render() {
8 | return (
9 |
10 |
11 |
12 |
13 | )
14 | }
15 | }
--------------------------------------------------------------------------------
/.client/components/TodoItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import classnames from 'classnames';
3 | import TodoTextInput from './TodoTextInput';
4 |
5 | export default class TodoItem extends Component {
6 | static propTypes = {
7 | todo: PropTypes.object.isRequired,
8 | editTodo: PropTypes.func.isRequired,
9 | deleteTodo: PropTypes.func.isRequired,
10 | markTodo: PropTypes.func.isRequired
11 | };
12 |
13 | constructor(props, context) {
14 | super(props, context);
15 | this.state = {
16 | editing: false
17 | };
18 | }
19 |
20 | handleDoubleClick() {
21 | this.setState({ editing: true });
22 | }
23 |
24 | handleSave(id, text) {
25 | if (text.length === 0) {
26 | this.props.deleteTodo(id);
27 | } else {
28 | this.props.editTodo(id, text);
29 | }
30 | this.setState({ editing: false });
31 | }
32 |
33 | render() {
34 | const {todo, markTodo, deleteTodo} = this.props;
35 |
36 | let element;
37 | if (this.state.editing) {
38 | element = (
39 | this.handleSave(todo._id, text)} />
42 | );
43 | } else {
44 | element = (
45 |
46 | markTodo(todo._id)} />
50 |
53 |
56 | );
57 | }
58 |
59 | return (
60 |
64 | {element}
65 |
66 | );
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/.client/components/TodoTextInput.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import classnames from 'classnames';
3 |
4 | export default class TodoTextInput extends Component {
5 | static propTypes = {
6 | onSave: PropTypes.func.isRequired,
7 | text: PropTypes.string,
8 | placeholder: PropTypes.string,
9 | editing: PropTypes.bool,
10 | newTodo: PropTypes.bool
11 | };
12 |
13 | constructor(props, context) {
14 | super(props, context);
15 | this.state = {
16 | text: this.props.text || ''
17 | };
18 | }
19 |
20 | handleSubmit(e) {
21 | const text = e.target.value.trim();
22 | if (e.which === 13) {
23 | this.props.onSave(text);
24 | if (this.props.newTodo) {
25 | this.setState({ text: '' });
26 | }
27 | }
28 | }
29 |
30 | handleChange(e) {
31 | this.setState({ text: e.target.value });
32 | }
33 |
34 | handleBlur(e) {
35 | if (!this.props.newTodo) {
36 | this.props.onSave(e.target.value);
37 | }
38 | }
39 |
40 | render() {
41 | return (
42 |
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/.client/constants/ActionTypes.js:
--------------------------------------------------------------------------------
1 | export const ADD_TODO = 'ADD_TODO';
2 | export const DELETE_TODO = 'DELETE_TODO';
3 | export const EDIT_TODO = 'EDIT_TODO';
4 | export const MARK_TODO = 'MARK_TODO';
5 | export const MARK_ALL = 'MARK_ALL';
6 | export const CLEAR_MARKED = 'CLEAR_MARKED';
7 |
--------------------------------------------------------------------------------
/.client/constants/TodoFilters.js:
--------------------------------------------------------------------------------
1 | export const SHOW_ALL = 'show_all';
2 | export const SHOW_MARKED = 'show_marked';
3 | export const SHOW_UNMARKED = 'show_unmarked';
4 |
--------------------------------------------------------------------------------
/.client/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import { connect } from 'react-redux';
4 |
5 | import TodoApp from "../components/TodoApp";
6 | import * as TodoActions from '../actions/TodoActions';
7 |
8 | function mapStateToProps(state) {
9 | return {
10 | todos: state.todos
11 | }
12 | }
13 |
14 | function mapDispatchToProps(dispatch) {
15 | return {actions: bindActionCreators(TodoActions, dispatch)};
16 | }
17 |
18 | export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);
19 |
--------------------------------------------------------------------------------
/.client/containers/Root.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import App from './App';
3 | import { createStore } from 'redux';
4 | import { Provider } from 'react-redux';
5 | import { todosReducer } from '../reducers';
6 | import { connectToMeteor } from 'meteoredux';
7 |
8 | Meteor.subscribe('todos');
9 |
10 | const store = createStore(todosReducer);
11 | connectToMeteor(store);
12 |
13 | export default class Root extends Component {
14 | render() {
15 | return (
16 |
17 | {() => }
18 |
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.client/index.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import Root from './containers/Root';
4 | import 'todomvc-app-css/index.css';
5 |
6 |
7 |
8 | Template.body.onRendered(function(){
9 | React.render(
10 | ,
11 | document.getElementById('root')
12 | )
13 | });
14 |
--------------------------------------------------------------------------------
/.client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todomvc",
3 | "version": "0.0.0",
4 | "description": "TodoMVC example for redux",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "node server.js",
8 | "dev": "webpack --watch"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/gaearon/redux.git"
13 | },
14 | "keywords": [
15 | "react",
16 | "reactjs",
17 | "hot",
18 | "reload",
19 | "hmr",
20 | "live",
21 | "edit",
22 | "webpack",
23 | "flux",
24 | "todomvc"
25 | ],
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/gaearon/redux/issues"
29 | },
30 | "homepage": "https://github.com/gaearon/redux#readme",
31 | "dependencies": {
32 | "classnames": "^2.1.2",
33 | "react": "^0.13.3",
34 | "redux": "^1.0.1",
35 | "react-redux" : "^0.8.2",
36 | "meteoredux": "~0.0.2"
37 | },
38 | "devDependencies": {
39 | "babel-core": "^5.5.8",
40 | "babel-loader": "^5.1.4",
41 | "node-libs-browser": "^0.5.2",
42 | "raw-loader": "^0.5.1",
43 | "react-hot-loader": "^1.2.7",
44 | "style-loader": "^0.12.3",
45 | "todomvc-app-css": "^2.0.1",
46 | "webpack": "^1.9.11",
47 | "webpack-dev-server": "^1.9.0"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/.client/reducers/index.js:
--------------------------------------------------------------------------------
1 | export { default as todosReducer } from './todos';
2 |
--------------------------------------------------------------------------------
/.client/reducers/todos.js:
--------------------------------------------------------------------------------
1 | import { ADD_TODO, DELETE_TODO, EDIT_TODO, MARK_TODO, MARK_ALL, CLEAR_MARKED } from '../constants/ActionTypes';
2 | import { bindReactiveData } from 'meteoredux'
3 |
4 | const initialState = {};
5 |
6 | function todos(state = initialState, action) {
7 |
8 | switch (action.type) {
9 | case ADD_TODO:
10 | Todos.insert({
11 | completed: false,
12 | text: action.text
13 | });
14 | //We have not changed the state here, so we return original state.
15 | return state;
16 |
17 | case DELETE_TODO:
18 | Todos.remove(action.id)
19 | return state;
20 |
21 | case EDIT_TODO:
22 | Todos.update(action.id, {$set: {text: action.text}})
23 | return state;
24 |
25 | case MARK_TODO:
26 | const todo = Todos.findOne(action.id)
27 | Todos.update(action.id, {$set: {completed: !todo.completed}})
28 | return state;
29 |
30 | case MARK_ALL:
31 | Todos.update({}, {$set: {completed: true}})
32 | return state;
33 |
34 | case CLEAR_MARKED:
35 | Todos.update({}, {$set: {completed: false}})
36 | return state;
37 |
38 |
39 | default:
40 | return state;
41 | }
42 | }
43 |
44 | function reactiveData(){
45 | return {
46 | todos: Todos.find({}).fetch()
47 | }
48 | }
49 | export default bindReactiveData(todos, reactiveData);
--------------------------------------------------------------------------------
/.client/server.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var WebpackDevServer = require('webpack-dev-server');
3 | var config = require('./webpack.config');
4 |
5 | new WebpackDevServer(webpack(config), {
6 | publicPath: config.output.publicPath,
7 | hot: true,
8 | historyApiFallback: true,
9 | stats: {
10 | colors: true
11 | }
12 | }).listen(3000, 'localhost', function (err) {
13 | if (err) {
14 | console.log(err);
15 | }
16 |
17 | console.log('Listening at localhost:3000');
18 | });
19 |
--------------------------------------------------------------------------------
/.client/stores/index.js:
--------------------------------------------------------------------------------
1 | export { default as todos } from './todos';
2 |
--------------------------------------------------------------------------------
/.client/stores/todos.js:
--------------------------------------------------------------------------------
1 | import { ADD_TODO, DELETE_TODO, EDIT_TODO, MARK_TODO, MARK_ALL, CLEAR_MARKED, TODO_CHANGED } from '../constants/ActionTypes';
2 |
3 | console.log("findAll");
4 | const initialState = Todo.findAll();
5 |
6 | export default function todos(state = initialState, action) {
7 | switch (action.type) {
8 |
9 | case ADD_TODO:
10 | Todos.insert({
11 | marked: false,
12 | text: action.text
13 | });
14 |
15 | return Todo.findAll();
16 |
17 | case DELETE_TODO:
18 | Todos.remove({_id: action.id})
19 | return Todo.findAll();
20 |
21 | case EDIT_TODO:
22 | Todos.update({_id: action.id}, {$set: {text: action.text}})
23 | return Todo.findAll();
24 |
25 | case MARK_TODO:
26 | const todo = Todos.findOne({_id: action.id})
27 | Todos.update({_id: action.id}, {$set: {marked: !todo.marked}})
28 | return Todo.findAll();
29 |
30 | case MARK_ALL:
31 | Todos.update({}, {$set: {marked: true}})
32 | return Todo.findAll();
33 |
34 | case CLEAR_MARKED:
35 | Todos.update({}, {$set: {marked: false}})
36 | return Todo.findAll();
37 |
38 | case TODO_CHANGED:
39 | return Todo.findAll();
40 |
41 | default:
42 | return state;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.client/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | devtool: 'eval',
6 | //entry: [
7 | // 'webpack-dev-server/client?http://localhost:3000',
8 | // 'webpack/hot/only-dev-server',
9 | // './index'
10 | //],
11 | entry: [
12 | './index'
13 | ],
14 | output: {
15 | path: path.join(__dirname, '..', 'client'),
16 | filename: 'bundle.js'
17 | },
18 | //plugins: [
19 | // new webpack.HotModuleReplacementPlugin(),
20 | // new webpack.NoErrorsPlugin()
21 | //],
22 | resolve: {
23 | extensions: ['', '.js']
24 | },
25 | module: {
26 | loaders: [{
27 | test: /\.js$/,
28 | loaders: ['react-hot', 'babel'],
29 | exclude: /node_modules/
30 | }, {
31 | test: /\.css?$/,
32 | loaders: ['style', 'raw']
33 | }]
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .meteor/local
2 | .meteor/meteorite
3 |
4 |
5 | .DS_Store
6 | .AppleDouble
7 | ._*
8 | .Trashes
9 | .Spotlight-V100
10 |
11 |
12 | .idea
13 |
14 |
15 | Thumb.db
16 | ehthumb.db
17 | Desktop.ini
18 | $RECYCLE.BIN/
19 |
20 | *.cab
21 | *.msi
22 | *.msm
23 | *.msp
24 |
25 |
26 | *~
27 | .directory
28 | client/bundle.js
--------------------------------------------------------------------------------
/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 |
--------------------------------------------------------------------------------
/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 1to4kou1rc2hfb107vsl7
8 |
--------------------------------------------------------------------------------
/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | # Check this file (and the other files in this directory) into your repository.
3 | #
4 | # 'meteor add' and 'meteor remove' will edit this file for you,
5 | # but you can also edit it by hand.
6 |
7 | meteor-platform
8 | autopublish
9 | insecure
10 |
--------------------------------------------------------------------------------
/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.1.0.2
2 |
--------------------------------------------------------------------------------
/.meteor/versions:
--------------------------------------------------------------------------------
1 | autopublish@1.0.3
2 | autoupdate@1.2.1
3 | base64@1.0.3
4 | binary-heap@1.0.3
5 | blaze@2.1.2
6 | blaze-tools@1.0.3
7 | boilerplate-generator@1.0.3
8 | callback-hook@1.0.3
9 | check@1.0.5
10 | ddp@1.1.0
11 | deps@1.0.7
12 | ejson@1.0.6
13 | fastclick@1.0.3
14 | geojson-utils@1.0.3
15 | html-tools@1.0.4
16 | htmljs@1.0.4
17 | http@1.1.0
18 | id-map@1.0.3
19 | insecure@1.0.3
20 | jquery@1.11.3_2
21 | json@1.0.3
22 | launch-screen@1.0.2
23 | livedata@1.0.13
24 | logging@1.0.7
25 | meteor@1.1.6
26 | meteor-platform@1.2.2
27 | minifiers@1.1.5
28 | minimongo@1.0.8
29 | mobile-status-bar@1.0.3
30 | mongo@1.1.0
31 | observe-sequence@1.0.6
32 | ordered-dict@1.0.3
33 | random@1.0.3
34 | reactive-dict@1.1.0
35 | reactive-var@1.0.5
36 | reload@1.1.3
37 | retry@1.0.3
38 | routepolicy@1.0.5
39 | session@1.1.0
40 | spacebars@1.0.6
41 | spacebars-compiler@1.0.6
42 | templating@1.1.1
43 | tracker@1.0.7
44 | ui@1.0.6
45 | underscore@1.0.3
46 | url@1.0.4
47 | webapp@1.2.0
48 | webapp-hashing@1.0.3
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Using [meteoredux](https://www.npmjs.com/package/meteoredux) to connect Redux and Meteor.
--------------------------------------------------------------------------------
/client/.git-keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhongqf/meteor-react-redux-example/b5b4da3e0cb83c78ad67b4e1757cfc9606a164a5/client/.git-keep
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 | Redux TodoMVC
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/models/todo.js:
--------------------------------------------------------------------------------
1 | Todos = new Mongo.Collection("todos");
2 |
3 |
4 |
5 | Todo = {
6 | findAll: function() {
7 | return Todos.find({}).fetch();
8 | }
9 | }
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | install:
2 | cd .client && npm install && webpack
3 |
4 | start:
5 | meteor
6 |
--------------------------------------------------------------------------------