├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── LICENSE
├── favicon.ico
├── karma.conf.js
├── package.json
├── readme.md
├── src
├── app.css
├── app.js
├── bootstrap.js
├── controller.js
├── controller.test.js
├── graph
│ ├── index.js
│ └── render.js
├── helpers.js
├── index.html
├── model.js
├── store.js
├── template.js
├── todo.js
└── view.js
└── webpack.config.babel.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", {"modules": false}],
4 | "es2016",
5 | "stage-2",
6 | "react"
7 | ],
8 | "env": {
9 | "test": {
10 | "plugins": [
11 | ["__coverage__", {"ignore": "*.+(test|stub).*"}]
12 | ]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | node_modules/
3 | dist
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "kentcdodds/best-practices",
4 | "kentcdodds/possible-errors",
5 | "kentcdodds/es6/best-practices",
6 | "kentcdodds/es6/possible-errors",
7 | "kentcdodds/import/best-practices",
8 | "kentcdodds/import/possible-errors",
9 | "kentcdodds/mocha",
10 | "kentcdodds/webpack",
11 | "kentcdodds/react",
12 | ],
13 | "rules": {
14 | // these are only here because I did not
15 | // want to update the entire codebase ¯\_(ツ)_/¯
16 | "func-names": 0,
17 | "no-var": 0,
18 | "func-style": 0,
19 | "comma-dangle": 0,
20 | "valid-jsdoc": 0,
21 | "vars-on-top": 0,
22 | "complexity": [2, 8],
23 | "max-depth": [2, 6],
24 | "consistent-return": 0,
25 | "id-match": 0,
26 | "import/newline-after-import": 0,
27 | // es6 stuff we technically can not do yet
28 | "strict": 0,
29 | "object-shorthand": 0,
30 | "prefer-arrow-callback": 0,
31 | "prefer-template": 0,
32 | "babel/object-shorthand": 0,
33 | },
34 | "globals": {
35 | "describe": false,
36 | "it": false,
37 | "expect": false,
38 | "$on": false,
39 | "beforeEach": false,
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.js text eol=lf
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage/
3 | dist
4 | .opt-in
5 | scripts/deploy
6 | package-log.json
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2016 Kent C. Dodds
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kentcdodds/es6-todomvc/bf2db416838858efe6c2edd3b7438e6f99c0b2d6/favicon.ico
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | process.env.BABEL_ENV = 'test'
2 | const webpackEnv = {test: true}
3 | const webpackConfig = require('./webpack.config.babel')(webpackEnv)
4 |
5 | const testGlob = 'src/**/*.test.js'
6 | const srcGlob = 'src/**/!(*.test|*.stub).js'
7 |
8 | module.exports = config => {
9 | config.set({
10 | basePath: '',
11 | frameworks: ['mocha', 'chai'],
12 | files: [testGlob, srcGlob],
13 | exclude: ['src/bootstrap.js'],
14 | preprocessors: {
15 | [testGlob]: ['webpack'],
16 | [srcGlob]: ['webpack'],
17 | },
18 | webpack: webpackConfig,
19 | webpackMiddleware: {noInfo: true},
20 | reporters: ['progress', 'coverage'],
21 | coverageReporter: {
22 | check: {
23 | global: {
24 | statements: 11,
25 | branches: 0,
26 | functions: 0,
27 | lines: 11,
28 | },
29 | },
30 | reporters: [
31 | {type: 'lcov', dir: 'coverage/', subdir: '.'},
32 | {type: 'json', dir: 'coverage/', subdir: '.'},
33 | {type: 'text-summary'},
34 | ],
35 | },
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: false,
40 | browsers: ['Chrome'],
41 | singleRun: true,
42 | concurrency: Infinity,
43 | })
44 | }
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "dependencies": {
4 | "d3": "3.5.17",
5 | "lodash": "4.14.1",
6 | "rd3": "0.7.0",
7 | "react": "15.3.0",
8 | "react-dom": "15.3.0",
9 | "todomvc-app-css": "2.0.6"
10 | },
11 | "devDependencies": {
12 | "babel-core": "6.13.2",
13 | "babel-loader": "6.2.4",
14 | "babel-plugin-__coverage__": "11.0.0",
15 | "babel-preset-es2015": "6.13.2",
16 | "babel-preset-es2016": "6.11.3",
17 | "babel-preset-react": "6.11.1",
18 | "babel-preset-stage-2": "6.13.0",
19 | "chai": "3.5.0",
20 | "cpy-cli": "1.0.1",
21 | "css-loader": "0.23.1",
22 | "eslint": "3.2.2",
23 | "eslint-config-kentcdodds": "^9.0.0",
24 | "extract-text-webpack-plugin": "2.0.0-beta.3",
25 | "ghooks": "1.3.2",
26 | "html-webpack-plugin": "2.22.0",
27 | "http-server": "0.9.0",
28 | "inline-manifest-webpack-plugin": "3.0.1",
29 | "karma": "1.1.2",
30 | "karma-chai": "0.1.0",
31 | "karma-chrome-launcher": "1.0.1",
32 | "karma-coverage": "1.1.1",
33 | "karma-mocha": "1.1.1",
34 | "karma-webpack": "1.7.0",
35 | "mocha": "3.0.1",
36 | "npm-run-all": "2.3.0",
37 | "offline-plugin": "3.4.2",
38 | "opt-cli": "1.5.1",
39 | "progress-bar-webpack-plugin": "1.9.0",
40 | "rimraf": "2.5.4",
41 | "style-loader": "0.13.1",
42 | "surge": "0.18.0",
43 | "webpack": "2.1.0-beta.20",
44 | "webpack-config-utils": "2.0.0",
45 | "webpack-dev-server": "2.1.0-beta.0",
46 | "webpack-validator": "2.2.7"
47 | },
48 | "config": {
49 | "ghooks": {
50 | "pre-commit": "opt --in pre-commit --exec \"npm run validate\""
51 | }
52 | },
53 | "scripts": {
54 | "predeploy": "npm run build",
55 | "deploy": "./scripts/deploy",
56 | "prebuild": "rimraf dist",
57 | "build": "webpack --env.prod -p",
58 | "postbuild": "cpy favicon.ico dist",
59 | "prebuild:dev": "rimraf dist",
60 | "build:dev": "webpack --env.dev",
61 | "postbuild:dev": "cpy favicon.ico dist",
62 | "start": "http-server dist",
63 | "dev": "webpack-dev-server --env.dev --hot",
64 | "debug": "node-nightly --inspect --debug-brk node_modules/.bin/webpack --env.debug",
65 | "debug:dev": "npm run debug -- --env.dev",
66 | "debug:prod": "npm run debug -- --env.prod",
67 | "test": "karma start",
68 | "watch:test": "npm test -- --auto-watch --no-single-run",
69 | "validate": "npm-run-all --parallel lint build test",
70 | "lint": "eslint .",
71 | "setup": "npm install && npm run validate",
72 | "setup:fem": "git checkout FEM/07.1-deploy-surge && npm install && npm run validate && rimraf dist coverage && git checkout FEM/00-original-project",
73 | "setup:workshop": "git checkout workshop/07-coverage && npm install && npm run validate && rimraf dist coverage && git checkout workshop/00-original-project",
74 | "setup:egghead": "git checkout prelesson/polyfill-promises && npm install && mkdir dist && npm run validate"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Webpack Workshop
2 |
3 | > Using the Vanilla JavaScript TodoMVC Example
4 |
5 | [![MIT License][license-badge]][LICENSE]
6 | [![Donate][donate-badge]][donate]
7 | [![Star on GitHub][github-star-badge]][github-star]
8 | [![Tweet][twitter-badge]][twitter]
9 |
10 |
11 |
12 | ## Purpose
13 |
14 | This was originally part of [an ES6 training](http://kcd.im/es6-intro-slides) by [Kent C. Dodds](https://twitter.com/kentcdodds)
15 |
16 | Now I'm using it to teach people about [Webpack](http://webpack.github.io/):
17 |
18 | - [Egghead.io lessons](http://kcd.im/egghead-webpack)
19 | - [Frontend Masters](http://kcd.im/fem-webpack)
20 |
21 | ## Thanks
22 |
23 | This codebase was originally taken from the TodoMVC project starting [here](https://github.com/tastejs/todomvc/tree/563d1e1b8cee5f6ec962ec43663cb66a72b69d76/examples/vanillajs). Big thanks to them!
24 |
25 | ## Latest Workshop
26 |
27 | This repo has been used to teach in several different places (egghead, Frontend Masters, etc.). If you're coming here
28 | to follow along with that, then I recommend you follow the project setup for that (see below). The most recent and
29 | up-to-date version of the workshop is [the Frontend Masters workshop](http://kcd.im/fem-webpack). This is a linear
30 | workshop and starts with the `FEM/00-original-project` branch. See
31 | [the slides](https://slides.com/kentcdodds/webpack-deep-dive).
32 |
33 | ## Project Setup
34 |
35 | This project assumes you have [NodeJS v6](http://nodejs.org/) or greater installed. You should
36 | also have [npm v3](https://www.npmjs.com/) or greater installed as well (this comes packaged
37 | with Node 6). You'll also need a recent version of [git](https://git-scm.com/) installed
38 | as well.
39 |
40 | You may have come to this project from different varying sources. There are a
41 | different series of branches for each workshop/course I've done. To get started with
42 | the project, start with this:
43 |
44 | 1. [Sign up](https://github.com/join) for a GitHub Account (if you don't already have one)
45 | 2. [Fork](https://help.github.com/articles/fork-a-repo/) this repo
46 | 3. [Clone](https://help.github.com/articles/cloning-a-repository/) your fork
47 | 4. In the directory you cloned the repository, run `git fetch --all`
48 |
49 | If you need help with these steps, you might check out
50 | [this free Egghead.io course](http://kcd.im/pull-request) which can help you get things going.
51 |
52 | Finally, based on which version of the project you're looking for (workshop, egghead, or
53 | Frontend Masters) you'll run one of the following commands in the cloned directory:
54 |
55 | - **ES6 Workshop**: `npm run setup:workshop`
56 | - **Egghead Course**: `npm run setup:egghead`
57 | - **Frontend Masters Workshop**: `npm run setup:fem`
58 |
59 | If you get any failures at this point something is wrong and needs to be fixed. Remember,
60 | [Google](https://google.com) and [StackOverflow](https://stackoverflow.com) are your friends.
61 |
62 | You might find it helpful to see a list of the available branches. Run: `git branch` for that.
63 |
64 | ## Notes
65 |
66 | Because Webpack 2 is currently in beta, there are issues with `peerDependencies`, so you’ll have to use npm version 3 to be able to install the dependencies.
67 |
68 | Running Webpack with `webpack -p` is not [showing the warnings](https://webpack.js.org/guides/migrating/#uglifyjsplugin-warnings) about the dead code elimination anymore. However, it is working as expected.
69 |
70 | ### Updates
71 |
72 | The ecosystem moves fast. So this section of the README will be dedicated to tracking changes in the ecosystem so you
73 | can follow the repo, but be aware of changes in the latest versions of the dependencies you'll be installing (or if
74 | there are better dependencies to use now).
75 |
76 | - All courses
77 | - `babel-preset-es2015-webpack` is no longer necessary for leveraging tree shaking. You can now just use
78 | `babel-preset-es2015` with a special configuration to indicate modules should not be transformed.
79 | [More info](https://github.com/kentcdodds/es6-todomvc/issues/13)
80 | - Egghead Course
81 | - Using `istanbul` is no longer necessary for checking code coverage and can actually be accomplished using special
82 | configuration in `karma.conf.js` with the `karma-coverage` plugin like
83 | [this](https://github.com/kentcdodds/es6-todomvc/blob/f4f790ef7602bf9de4620841848d91f5213e647e/karma.conf.js#L22-L29).
84 |
85 | ### Contributing
86 |
87 | Super nice that you're interested in contributing. Unfortunately things are pretty complex with how things are set up
88 | with branches for each section of the workshop. So feel free to file pull requests to indicate what needs to be changed
89 | but if I decide to implement a change in the workshop code, I'll probably just have to manually make the updates.
90 | Thanks!
91 |
92 | ## Windows 10 Setup Instructions
93 |
94 | 1. Fork and clone this repository
95 | 2. Download Git Bash
96 | 3. Follow the instructions on this page to clone all branches at once in Git Bash: https://stackoverflow.com/questions/40310932/git-hub-clone-all-branches-at-once
97 | 4. In Git Bash: run `cd es6-todomvc`
98 | 5. Run `npm run setup:fem` (this will fail, but there is a workaround)
99 | 6. After that fails:
100 |
101 | * In **Git Bash** run `git stash`
102 | * run `git checkout FEM/00-original-project --force`
103 | * Make sure http-server is installed globally: `npm i -g http-server`
104 | * run `http-server --silent -c-1 -p 3084` (or whatever port number you want to use). If the port number you are trying to use is already in use, it will give you a nasty error that says something like: Error: listen EADDRINUSE 0.0.0.0:8081
105 | * Open `http://localhost:3084/` or change the URL to indicate the port number you wish to use
106 | * In your package.json file, add `-p 3084` to the end of your "start" script
107 | * Add `open http://localhost:3084/ && ` to the beginning of your start script (make sure there is a space between `&&` and `http-server`
108 | * now you can just run `npm start` and the app should load up at `http://localhost:3084`
109 |
110 | ## LICENSE
111 |
112 | MIT
113 |
114 | [license-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
115 | [license]: https://github.com/kentcdodds/es6-todomvc/blob/master/LICENSE
116 | [donate-badge]: https://img.shields.io/badge/%EF%BC%84-support-green.svg?style=flat-square
117 | [donate]: http://kcd.im/donate
118 | [github-star-badge]: https://img.shields.io/github/stars/kentcdodds/es6-todomvc.svg?style=social&label=Star
119 | [github-star]: https://github.com/kentcdodds/es6-todomvc/stargazers
120 | [twitter]: https://twitter.com/intent/tweet?text=Check%20out%20this%20fantastic%20webpack%20workshop!%20http://kcd.im/webpack-workshop-repo%20%F0%9F%98%8E
121 | [twitter-badge]: https://img.shields.io/twitter/url/https/kcd.im/webpack-workshop-repo.svg?style=social
122 |
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
1 | .toggle-graph {
2 | float: left;
3 | margin-left: 16px;
4 | cursor: pointer;
5 | position: relative;
6 | z-index: 1;
7 | }
8 | .toggle-graph svg {
9 | height: 20px;
10 | width: 20px;
11 | }
12 | .toggle-graph svg path {
13 | fill: #777;
14 | }
15 |
16 | .toggle-graph.active svg path,
17 | .toggle-graph:hover svg path,
18 | .toggle-graph:focus svg path {
19 | fill: black;
20 | }
21 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import 'todomvc-app-css/index.css'
2 | import './app.css'
3 |
4 | import {$on} from './helpers'
5 | import {updateTodo} from './todo'
6 | import toggleGraph from './graph'
7 |
8 | export function onLoad() { // eslint-disable-line import/prefer-default-export
9 | updateTodo()
10 | const toggleGraphButton = document.querySelector('.toggle-graph')
11 | $on(
12 | toggleGraphButton,
13 | 'click',
14 | () => {
15 | const active = toggleGraph()
16 | if (active) {
17 | toggleGraphButton.classList.add('active')
18 | } else {
19 | toggleGraphButton.classList.remove('active')
20 | }
21 | },
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/bootstrap.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console:0 */
2 | import {install as offlineInstall} from 'offline-plugin/runtime'
3 | import {onLoad} from './app'
4 | import {$on} from './helpers'
5 |
6 | // this is only relevant when using `hot` mode with webpack
7 | // special thanks to Eric Clemmons: https://github.com/ericclemmons/webpack-hot-server-example
8 | const reloading = document.readyState === 'complete'
9 | if (module.hot) {
10 | module.hot.accept(function(err) {
11 | console.log('❌ HMR Error:', err)
12 | })
13 | if (reloading) {
14 | console.log('🔁 HMR Reloading.')
15 | onLoad()
16 | } else {
17 | console.info('✅ HMR Enabled.')
18 | bootstrap()
19 | }
20 | } else {
21 | console.info('❌ HMR Not Supported.')
22 | bootstrap()
23 | }
24 |
25 | function bootstrap() {
26 | $on(window, 'load', onLoad)
27 | $on(window, 'hashchange', onLoad)
28 | if (process.env.NODE_ENV === 'production') {
29 | offlineInstall()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/controller.js:
--------------------------------------------------------------------------------
1 | export default Controller
2 |
3 | /**
4 | * Takes a model and view and acts as the controller between them
5 | *
6 | * @constructor
7 | * @param {object} model The model instance
8 | * @param {object} view The view instance
9 | */
10 | function Controller(model, view) {
11 | var that = this
12 | that.model = model
13 | that.view = view
14 |
15 | that.view.bind('newTodo', function(title) {
16 | that.addItem(title)
17 | })
18 |
19 | that.view.bind('itemEdit', function(item) {
20 | that.editItem(item.id)
21 | })
22 |
23 | that.view.bind('itemEditDone', function(item) {
24 | that.editItemSave(item.id, item.title)
25 | })
26 |
27 | that.view.bind('itemEditCancel', function(item) {
28 | that.editItemCancel(item.id)
29 | })
30 |
31 | that.view.bind('itemRemove', function(item) {
32 | that.removeItem(item.id)
33 | })
34 |
35 | that.view.bind('itemToggle', function(item) {
36 | that.toggleComplete(item.id, item.completed)
37 | })
38 |
39 | that.view.bind('removeCompleted', function() {
40 | that.removeCompletedItems()
41 | })
42 |
43 | that.view.bind('toggleAll', function(status) {
44 | that.toggleAll(status.completed)
45 | })
46 | }
47 |
48 | /**
49 | * Loads and initialises the view
50 | *
51 | * @param {string} '' | 'active' | 'completed'
52 | */
53 | Controller.prototype.setView = function(locationHash) {
54 | var route = locationHash.split('/')[1]
55 | var page = route || ''
56 | this._updateFilterState(page)
57 | }
58 |
59 | /**
60 | * An event to fire on load. Will get all items and display them in the
61 | * todo-list
62 | */
63 | Controller.prototype.showAll = function() {
64 | var that = this
65 | that.model.read(function(data) {
66 | that.view.render('showEntries', data)
67 | })
68 | }
69 |
70 | /**
71 | * Renders all active tasks
72 | */
73 | Controller.prototype.showActive = function() {
74 | var that = this
75 | that.model.read({completed: false}, function(data) {
76 | that.view.render('showEntries', data)
77 | })
78 | }
79 |
80 | /**
81 | * Renders all completed tasks
82 | */
83 | Controller.prototype.showCompleted = function() {
84 | var that = this
85 | that.model.read({completed: true}, function(data) {
86 | that.view.render('showEntries', data)
87 | })
88 | }
89 |
90 | /**
91 | * An event to fire whenever you want to add an item. Simply pass in the event
92 | * object and it'll handle the DOM insertion and saving of the new item.
93 | */
94 | Controller.prototype.addItem = function(title) {
95 | var that = this
96 |
97 | if (title.trim() === '') {
98 | return
99 | }
100 |
101 | that.model.create(title, function() {
102 | that.view.render('clearNewTodo')
103 | that._filter(true)
104 | })
105 | }
106 |
107 | /*
108 | * Triggers the item editing mode.
109 | */
110 | Controller.prototype.editItem = function(id) {
111 | var that = this
112 | that.model.read(id, function(data) {
113 | that.view.render('editItem', {id, title: data[0].title})
114 | })
115 | }
116 |
117 | /*
118 | * Finishes the item editing mode successfully.
119 | */
120 | Controller.prototype.editItemSave = function(id, title) {
121 | var that = this
122 | if (title.trim()) {
123 | that.model.update(id, {title}, function() {
124 | that.view.render('editItemDone', {id, title})
125 | })
126 | } else {
127 | that.removeItem(id)
128 | }
129 | }
130 |
131 | /*
132 | * Cancels the item editing mode.
133 | */
134 | Controller.prototype.editItemCancel = function(id) {
135 | var that = this
136 | that.model.read(id, function(data) {
137 | that.view.render('editItemDone', {id, title: data[0].title})
138 | })
139 | }
140 |
141 | /**
142 | * By giving it an ID it'll find the DOM element matching that ID,
143 | * remove it from the DOM and also remove it from storage.
144 | *
145 | * @param {number} id The ID of the item to remove from the DOM and
146 | * storage
147 | */
148 | Controller.prototype.removeItem = function(id) {
149 | var that = this
150 | that.model.remove(id, function() {
151 | that.view.render('removeItem', id)
152 | })
153 |
154 | that._filter()
155 | }
156 |
157 | /**
158 | * Will remove all completed items from the DOM and storage.
159 | */
160 | Controller.prototype.removeCompletedItems = function() {
161 | var that = this
162 | that.model.read({completed: true}, function(data) {
163 | data.forEach(function(item) {
164 | that.removeItem(item.id)
165 | })
166 | })
167 |
168 | that._filter()
169 | }
170 |
171 | /**
172 | * Give it an ID of a model and a checkbox and it will update the item
173 | * in storage based on the checkbox's state.
174 | *
175 | * @param {number} id The ID of the element to complete or uncomplete
176 | * @param {object} checkbox The checkbox to check the state of complete
177 | * or not
178 | * @param {boolean|undefined} silent Prevent re-filtering the todo items
179 | */
180 | Controller.prototype.toggleComplete = function(id, completed, silent) {
181 | var that = this
182 | that.model.update(id, {completed}, function() {
183 | that.view.render('elementComplete', {
184 | id,
185 | completed,
186 | })
187 | })
188 |
189 | if (!silent) {
190 | that._filter()
191 | }
192 | }
193 |
194 | /**
195 | * Will toggle ALL checkboxes' on/off state and completeness of models.
196 | * Just pass in the event object.
197 | */
198 | Controller.prototype.toggleAll = function(completed) {
199 | var that = this
200 | that.model.read({completed: !completed}, function(data) {
201 | data.forEach(function(item) {
202 | that.toggleComplete(item.id, completed, true)
203 | })
204 | })
205 |
206 | that._filter()
207 | }
208 |
209 | /**
210 | * Updates the pieces of the page which change depending on the remaining
211 | * number of todos.
212 | */
213 | Controller.prototype._updateCount = function() {
214 | var that = this
215 | that.model.getCount(function(todos) {
216 | that.view.render('updateElementCount', todos.active)
217 | that.view.render('clearCompletedButton', {
218 | completed: todos.completed,
219 | visible: todos.completed > 0
220 | })
221 |
222 | that.view.render('toggleAll', {checked: todos.completed === todos.total})
223 | that.view.render('contentBlockVisibility', {visible: todos.total > 0})
224 | })
225 | }
226 |
227 | /**
228 | * Re-filters the todo items, based on the active route.
229 | * @param {boolean|undefined} force forces a re-painting of todo items.
230 | */
231 | Controller.prototype._filter = function(force) {
232 | var activeRoute = this._activeRoute.charAt(0).toUpperCase() + this._activeRoute.substr(1)
233 |
234 | // Update the elements on the page, which change with each completed todo
235 | this._updateCount()
236 |
237 | // If the last active route isn't "All", or we're switching routes, we
238 | // re-create the todo item elements, calling:
239 | // this.show[All|Active|Completed]();
240 | if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) {
241 | this['show' + activeRoute]()
242 | }
243 |
244 | this._lastActiveRoute = activeRoute
245 | }
246 |
247 | /**
248 | * Simply updates the filter nav's selected states
249 | */
250 | Controller.prototype._updateFilterState = function(currentPage) {
251 | // Store a reference to the active route, allowing us to re-filter todo
252 | // items as they are marked complete or incomplete.
253 | currentPage = currentPage.split('?')[0]
254 | this._activeRoute = currentPage
255 |
256 | if (currentPage === '') {
257 | this._activeRoute = 'All'
258 | }
259 |
260 | this._filter()
261 |
262 | this.view.render('setFilter', currentPage)
263 | }
264 |
--------------------------------------------------------------------------------
/src/controller.test.js:
--------------------------------------------------------------------------------
1 | import Controller from './controller'
2 |
3 | describe('controller', () => {
4 | it('exists', () => {
5 | expect(Controller).to.exist
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/src/graph/index.js:
--------------------------------------------------------------------------------
1 | import {subscribe, getTodo} from '../todo'
2 |
3 | let graphArea
4 | const unsubscribe = {
5 | store: null,
6 | todo: null,
7 | }
8 |
9 | export default toggleGraph
10 |
11 | function toggleGraph() {
12 | if (graphArea) {
13 | graphArea.remove()
14 | graphArea = null
15 | unsubscribe.store()
16 | unsubscribe.todo()
17 | return false
18 | } else {
19 | graphArea = document.createElement('div')
20 | document.body.querySelector('.graph-area-container').appendChild(graphArea)
21 | const {storage} = getTodo()
22 | loadAndRenderGraph(graphArea, storage)
23 | updateTodoSubscription()
24 | updateStoreSubscription(storage)
25 | return true
26 | }
27 | }
28 |
29 | function updateTodoSubscription() {
30 | if (unsubscribe.todo) {
31 | unsubscribe.todo()
32 | }
33 | unsubscribe.todo = subscribe(function onTodoUpdate() {
34 | const {storage} = getTodo()
35 | updateStoreSubscription(storage)
36 | loadAndRenderGraph(graphArea, storage)
37 | })
38 | }
39 |
40 | function updateStoreSubscription(store) {
41 | if (unsubscribe.store) {
42 | unsubscribe.store()
43 | }
44 | unsubscribe.store = store.subscribe(function onStoreUpdate() {
45 | loadAndRenderGraph(graphArea, store)
46 | })
47 | }
48 |
49 | function loadAndRenderGraph(node, store) {
50 | System.import('./render').then(({default: renderGraph}) => {
51 | renderGraph(node, store)
52 | })
53 | }
54 |
--------------------------------------------------------------------------------
/src/graph/render.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import {PieChart} from 'rd3'
4 | import {chain} from 'lodash'
5 |
6 | export default updateGraph
7 |
8 | function updateGraph(node, store) {
9 | store.findAll(todos => {
10 | ReactDOM.render(
11 |