80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/todomvc/preact/src/model.js:
--------------------------------------------------------------------------------
1 | import { uuid, store } from './util';
2 |
3 | // note: commented out localStorage persistence as it mucks up tests.
4 |
5 | export default () => {
6 | let onChanges = [];
7 |
8 | function inform() {
9 | for (let i=onChanges.length; i--; ) {
10 | onChanges[i](model);
11 | }
12 | }
13 |
14 | let model = {
15 | todos: [],
16 |
17 | onChanges: [],
18 |
19 | subscribe(fn) {
20 | onChanges.push(fn);
21 | },
22 |
23 | addTodo(title) {
24 | model.todos = model.todos.concat({
25 | id: uuid(),
26 | title,
27 | completed: false
28 | });
29 | inform();
30 | },
31 |
32 | toggleAll(completed) {
33 | model.todos = model.todos.map(
34 | todo => ({ ...todo, completed })
35 | );
36 | inform();
37 | },
38 |
39 | toggle(todoToToggle) {
40 | model.todos = model.todos.map( todo => (
41 | todo !== todoToToggle ? todo : ({ ...todo, completed: !todo.completed })
42 | ) );
43 | inform();
44 | },
45 |
46 | destroy(todo) {
47 | model.todos = model.todos.filter( t => t !== todo );
48 | inform();
49 | },
50 |
51 | save(todoToSave, title) {
52 | model.todos = model.todos.map( todo => (
53 | todo !== todoToSave ? todo : ({ ...todo, title })
54 | ));
55 | inform();
56 | },
57 |
58 | clearCompleted() {
59 | model.todos = model.todos.filter( todo => !todo.completed );
60 | inform();
61 | }
62 | };
63 |
64 | return model;
65 | };
66 |
--------------------------------------------------------------------------------
/todomvc/preact/src/util.js:
--------------------------------------------------------------------------------
1 | export function uuid() {
2 | let uuid = '';
3 | for (let i=0; i<32; i++) {
4 | let random = Math.random() * 16 | 0;
5 | if (i === 8 || i === 12 || i === 16 || i === 20) {
6 | uuid += '-';
7 | }
8 | uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16);
9 | }
10 | return uuid;
11 | }
12 |
13 | export function pluralize(count, word) {
14 | return count === 1 ? word : word + 's';
15 | }
16 |
17 | export function store(namespace, data) {
18 | if (data) {
19 | return localStorage.setItem(namespace, JSON.stringify(data));
20 | }
21 |
22 | let store = localStorage.getItem(namespace);
23 | return (store && JSON.parse(store)) || [];
24 | }
25 |
--------------------------------------------------------------------------------
/todomvc/ractive/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todomvc-ractive",
3 | "dependencies": {
4 | "todomvc-common": "~0.1.4",
5 | "ractive": "~0.3.5",
6 | "director": "~1.2.0"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/todomvc/ractive/bower_components/todomvc-common/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rabbotio/todomvc-perf/f21ada979b180f5ea67dd1c78dfee3ef91f08bdc/todomvc/ractive/bower_components/todomvc-common/bg.png
--------------------------------------------------------------------------------
/todomvc/ractive/css/app.css:
--------------------------------------------------------------------------------
1 | input[type="checkbox"] {
2 | outline: none;
3 | }
4 |
5 | label {
6 | -webkit-user-select: none;
7 | -moz-user-select: none;
8 | -ms-user-select: none;
9 | user-select: none;
10 | }
11 |
--------------------------------------------------------------------------------
/todomvc/ractive/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Ractive.js • TodoMVC
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
72 |
73 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/todomvc/ractive/js/app.js:
--------------------------------------------------------------------------------
1 | /*global window, Ractive */
2 | (function (window, Ractive) {
3 | 'use strict';
4 |
5 | // Create some filter functions, which we'll need later
6 | var filters = {
7 | completed: function (item) { return item.completed; },
8 | active: function (item) { return !item.completed; }
9 | };
10 |
11 | // The keycode for the 'enter' and 'escape' keys
12 | var ENTER_KEY = 13;
13 | var ESCAPE_KEY = 27;
14 |
15 | // Create our Ractive instance
16 | var todoList = new Ractive({
17 | // Specify a target element - an ID, a CSS selector, or the element itself
18 | el: 'todoapp',
19 |
20 | // Specify a template, or the ID of a script tag containing the template
21 | template: '#main',
22 |
23 | // This is our viewmodel - as well as our list of tasks (which gets added
24 | // later from localStorage - see persistence.js), it includes helper
25 | // functions and computed values
26 | data: {
27 | filter: function (item) {
28 | // Because we're doing `this.get('currentFilter')`, Ractive understands
29 | // that this function needs to be re-executed reactively when the value of
30 | // `currentFilter` changes
31 | var currentFilter = this.get('currentFilter');
32 |
33 | if (currentFilter === 'all') {
34 | return true;
35 | }
36 |
37 | return filters[currentFilter](item);
38 | },
39 |
40 | // completedTasks() and activeTasks() are computed values, that will update
41 | // our app view reactively whenever `items` changes (including changes to
42 | // child properties like `items[1].completed`)
43 | completedTasks: function () {
44 | return this.get('items').filter(filters.completed);
45 | },
46 |
47 | activeTasks: function () {
48 | return this.get('items').filter(filters.active);
49 | },
50 |
51 | // By default, show all tasks. This value changes when the route changes
52 | // (see routes.js)
53 | currentFilter: 'all'
54 | },
55 |
56 | // We can define custom events. Here, we're defining an `enter` event,
57 | // and an `escape` event, which fire when the user hits those keys while
58 | // an input is focused:
59 | //
60 | //
61 | events: (function () {
62 | var makeCustomEvent = function (keyCode) {
63 | return function (node, fire) {
64 | var keydownHandler = function (event) {
65 | if (event.which === keyCode) {
66 | fire({
67 | node: node,
68 | original: event
69 | });
70 | }
71 | };
72 |
73 | node.addEventListener('keydown', keydownHandler, false);
74 |
75 | return {
76 | teardown: function () {
77 | node.removeEventListener('keydown', keydownHandler, false);
78 | }
79 | };
80 | };
81 | };
82 |
83 | return {
84 | enter: makeCustomEvent(ENTER_KEY),
85 | escape: makeCustomEvent(ESCAPE_KEY)
86 | };
87 | }())
88 | });
89 |
90 |
91 | // Here, we're defining how to respond to user interactions. Unlike many
92 | // libraries, where you use CSS selectors to dictate what event corresponds
93 | // to what action, in Ractive the 'meaning' of the event is baked into the
94 | // template itself (e.g. ).
95 | todoList.on({
96 |
97 | // Removing a todo is as straightforward as splicing it out of the array -
98 | // Ractive intercepts calls to array mutator methods and updates the view
99 | // accordingly. The DOM is updated in the most efficient manner possible.
100 | remove: function (event, index) {
101 | this.get('items').splice(index, 1);
102 | },
103 |
104 | // The `event` object contains useful properties for (e.g.) retrieving
105 | // data from the DOM
106 | newTodo: function (event) {
107 | var description = event.node.value.trim();
108 |
109 | if (!description) {
110 | return;
111 | }
112 |
113 | this.get('items').push({
114 | description: description,
115 | completed: false
116 | });
117 |
118 | event.node.value = '';
119 | },
120 |
121 | edit: function (event) {
122 | this.set(event.keypath + '.editing', true);
123 | this.nodes.edit.value = event.context.description;
124 | },
125 |
126 | submit: function (event) {
127 | var description = event.node.value.trim();
128 |
129 | if (!description) {
130 | this.get('items').splice(event.index.i, 1);
131 | return;
132 | }
133 |
134 | this.set(event.keypath + '.description', description);
135 | this.set(event.keypath + '.editing', false);
136 | },
137 |
138 | cancel: function (event) {
139 | this.set(event.keypath + '.editing', false);
140 | },
141 |
142 | clearCompleted: function () {
143 | var items = this.get('items');
144 | var i = items.length;
145 |
146 | while (i--) {
147 | if (items[i].completed) {
148 | items.splice(i, 1);
149 | }
150 | }
151 | },
152 |
153 | toggleAll: function (event) {
154 | var i = this.get('items').length;
155 | var completed = event.node.checked;
156 | var changeHash = {};
157 |
158 | while (i--) {
159 | changeHash['items[' + i + '].completed'] = completed;
160 | }
161 |
162 | this.set(changeHash);
163 | }
164 | });
165 |
166 | window.todoList = todoList;
167 |
168 | })(window, Ractive);
169 |
--------------------------------------------------------------------------------
/todomvc/ractive/js/persistence.js:
--------------------------------------------------------------------------------
1 | /*global window, todoList */
2 | (function (window, todoList) {
3 | 'use strict';
4 |
5 | // In Ractive, 'models' are usually just POJOs - plain old JavaScript objects.
6 | // Our todo list is simply an array of objects, which is handy for fetching
7 | // and persisting from/to localStorage
8 |
9 | var items, localStorage, removeEditingState;
10 |
11 | // Firefox throws a SecurityError if you try to access localStorage while
12 | // cookies are disabled
13 | try {
14 | localStorage = window.localStorage;
15 | } catch (err) {
16 | todoList.set('items', []);
17 | return;
18 | }
19 |
20 | if (localStorage) {
21 | items = JSON.parse(localStorage.getItem('todos-ractive')) || [];
22 |
23 | // Editing state should not be persisted, so we remove it
24 | // (https://github.com/tastejs/todomvc/blob/gh-pages/app-spec.md#persistence)
25 | removeEditingState = function (item) {
26 | return {
27 | description: item.description,
28 | completed: item.completed
29 | };
30 | };
31 |
32 | // Whenever the model changes (including child properties like
33 | // `items[1].completed`)...
34 | todoList.observe('items', function (items) {
35 |
36 | // ...we persist it to localStorage
37 | localStorage.setItem('todos-ractive', JSON.stringify(items.map(removeEditingState)));
38 | });
39 | } else {
40 | items = [];
41 | }
42 |
43 | todoList.set('items', items);
44 |
45 | })(window, todoList);
46 |
--------------------------------------------------------------------------------
/todomvc/ractive/js/routes.js:
--------------------------------------------------------------------------------
1 | /*global window, Router, todoList */
2 | (function (window, Router, todoList) {
3 | 'use strict';
4 |
5 | // We're using https://github.com/flatiron/director for routing
6 |
7 | var router = new Router({
8 | '/active': function () {
9 | todoList.set('currentFilter', 'active');
10 | },
11 | '/completed': function () {
12 | todoList.set('currentFilter', 'completed');
13 | }
14 | });
15 |
16 | router.configure({
17 | notfound: function () {
18 | window.location.hash = '';
19 | todoList.set('currentFilter', 'all');
20 | }
21 | });
22 |
23 | router.init();
24 |
25 | })(window, Router, todoList);
26 |
--------------------------------------------------------------------------------
/todomvc/ractive/readme.md:
--------------------------------------------------------------------------------
1 | # Ractive.js TodoMVC app
2 |
3 | > Ractive.js solves some of the biggest headaches in web development – data binding, efficient DOM updates, event handling – and does so with almost no learning curve.
4 |
5 | > _[Ractive.js - ractivejs.org](http://ractivejs.org)_
6 |
7 |
8 | ## Learning Ractive.js
9 |
10 | [Try the 60 second setup](https://github.com/Rich-Harris/Ractive/wiki/60-second-setup), or [follow the interactive tutorials](http://learn.ractivejs.org).
11 |
12 | You can find the [API documentation on GitHub](https://github.com/Rich-Harris/Ractive/wiki).
13 |
14 | If you have questions, try [Stack Overflow](http://stackoverflow.com/questions/tagged/ractivejs) or [@RactiveJS on Twitter](http://twitter.com/RactiveJS).
15 |
16 | _If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
17 |
18 |
19 | ## Implementation
20 |
21 | Ractive.js isn't an MVC framework in the traditional sense. There are no Model classes, just a plain old array of task objects, and there is only one view object. The app lives in a single file, with two tiny extra files to handle persistence and routing.
22 |
23 | This is because Ractive is optimised for developer sanity (as well as performance!). It was initially developed to create interactive news apps at [theguardian.com](http://theguardian.com), which have to be built against punishing deadlines and moving goalposts. Because it embraces reactive programming principles, the developer has less to worry about. Ractive's API is totally straightforward - you can learn it in 5 minutes and master it in a few hours.
24 |
25 | It has a number of innovative features: animations, easier event handling, declarative transitions, first-class SVG support, logical expressions in templates with sophisticated dependency tracking. For many developers, it hits the sweet spot between the flexibility of a library like Backbone and the power of a framework like Angular.
26 |
27 |
28 | ## Credit
29 |
30 | This TodoMVC application was created by [Rich Harris](http://rich-harris.co.uk).
31 |
--------------------------------------------------------------------------------
/todomvc/react/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/director
2 | !node_modules/director/build/director.js
3 |
4 | node_modules/react
5 | !node_modules/react/dist/react-with-addons.js
6 | !node_modules/react/dist/JSXTransformer.js
7 | node_modules/todomvc-app-css
8 | !node_modules/todomvc-app-css/index.css
9 |
10 | node_modules/todomvc-common
11 | !node_modules/todomvc-common/base.css
12 | !node_modules/todomvc-common/base.js
13 |
--------------------------------------------------------------------------------
/todomvc/react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React • TodoMVC
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/todomvc/react/js/app.js:
--------------------------------------------------------------------------------
1 | /*jshint quotmark:false */
2 | /*jshint white:false */
3 | /*jshint trailing:false */
4 | /*jshint newcap:false */
5 | /*global React, Router, ReactDOM*/
6 | var app = app || {};
7 |
8 | (function () {
9 | 'use strict';
10 |
11 | app.ALL_TODOS = 'all';
12 | app.ACTIVE_TODOS = 'active';
13 | app.COMPLETED_TODOS = 'completed';
14 | var TodoFooter = React.createFactory(app.TodoFooter);
15 | var TodoItem = React.createFactory(app.TodoItem);
16 |
17 | var ENTER_KEY = 13;
18 |
19 | var TodoApp = React.createClass({
20 | getInitialState: function () {
21 | return {
22 | nowShowing: app.ALL_TODOS,
23 | editing: null,
24 | newTodo: ''
25 | };
26 | },
27 |
28 | componentDidMount: function () {
29 | var setState = this.setState;
30 | var router = Router({
31 | '/': setState.bind(this, {nowShowing: app.ALL_TODOS}),
32 | '/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}),
33 | '/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS})
34 | });
35 | router.init('/');
36 | },
37 |
38 | handleChange: function (event) {
39 | this.setState({newTodo: event.target.value});
40 | },
41 |
42 | handleNewTodoKeyDown: function (event) {
43 | if (event.keyCode !== ENTER_KEY) {
44 | return;
45 | }
46 |
47 | event.preventDefault();
48 |
49 | var val = this.state.newTodo.trim();
50 |
51 | if (val) {
52 | this.props.model.addTodo(val);
53 | this.setState({newTodo: ''});
54 | }
55 | },
56 |
57 | toggleAll: function (event) {
58 | var checked = event.target.checked;
59 | this.props.model.toggleAll(checked);
60 | },
61 |
62 | toggle: function (todoToToggle) {
63 | this.props.model.toggle(todoToToggle);
64 | },
65 |
66 | destroy: function (todo) {
67 | this.props.model.destroy(todo);
68 | },
69 |
70 | edit: function (todo) {
71 | this.setState({editing: todo.id});
72 | },
73 |
74 | save: function (todoToSave, text) {
75 | this.props.model.save(todoToSave, text);
76 | this.setState({editing: null});
77 | },
78 |
79 | cancel: function () {
80 | this.setState({editing: null});
81 | },
82 |
83 | clearCompleted: function () {
84 | this.props.model.clearCompleted();
85 | },
86 |
87 | render: function () {
88 | var footer;
89 | var main;
90 | var todos = this.props.model.todos;
91 |
92 | var shownTodos = todos.filter(function (todo) {
93 | switch (this.state.nowShowing) {
94 | case app.ACTIVE_TODOS:
95 | return !todo.completed;
96 | case app.COMPLETED_TODOS:
97 | return todo.completed;
98 | default:
99 | return true;
100 | }
101 | }, this);
102 |
103 | var todoItems = shownTodos.map(function (todo) {
104 | return (
105 | TodoItem({
106 | key: todo.id,
107 | todo: todo,
108 | onToggle: this.toggle.bind(this, todo),
109 | onDestroy: this.destroy.bind(this, todo),
110 | onEdit: this.edit.bind(this, todo),
111 | editing: this.state.editing === todo.id,
112 | onSave: this.save.bind(this, todo),
113 | onCancel: this.cancel}
114 | )
115 | );
116 | }, this);
117 |
118 | var activeTodoCount = todos.reduce(function (accum, todo) {
119 | return todo.completed ? accum : accum + 1;
120 | }, 0);
121 |
122 | var completedCount = todos.length - activeTodoCount;
123 |
124 | if (activeTodoCount || completedCount) {
125 | footer =
126 | TodoFooter({
127 | count: activeTodoCount,
128 | completedCount: completedCount,
129 | nowShowing: this.state.nowShowing,
130 | onClearCompleted: this.clearCompleted}
131 | );
132 | }
133 |
134 | if (todos.length) {
135 | main = (
136 | React.createElement('section', {className: "main"}, [
137 | React.createElement('input', {
138 | className: "toggle-all",
139 | type: "checkbox",
140 | onChange: this.toggleAll,
141 | checked: activeTodoCount === 0}
142 | ),
143 | React.createElement('ul', {className: "todo-list"}, [
144 | todoItems
145 | ])
146 | ])
147 | );
148 | }
149 |
150 | return (
151 | React.createElement('div', null, [
152 | React.createElement('header', {className: "header"}, [
153 | React.createElement('h1', null, ["todos"]),
154 | React.createElement('input', {
155 | className: "new-todo",
156 | placeholder: "What needs to be done?",
157 | value: this.state.newTodo,
158 | onKeyDown: this.handleNewTodoKeyDown,
159 | onChange: this.handleChange,
160 | autoFocus: true}
161 | )
162 | ]),
163 | main,
164 | footer
165 | ])
166 | );
167 | }
168 | });
169 |
170 | var model = new app.TodoModel('react-todos');
171 |
172 | function render() {
173 | ReactDOM.render(
174 | React.createFactory(TodoApp)({model: model}),
175 | document.getElementsByClassName('todoapp')[0]
176 | );
177 | }
178 |
179 | window.Utils = app.Utils
180 | model.subscribe(render);
181 | render();
182 | })();
183 |
--------------------------------------------------------------------------------
/todomvc/react/js/app.jsx:
--------------------------------------------------------------------------------
1 | /*jshint quotmark:false */
2 | /*jshint white:false */
3 | /*jshint trailing:false */
4 | /*jshint newcap:false */
5 | /*global React, Router, ReactDOM*/
6 | var app = app || {};
7 |
8 | (function () {
9 | 'use strict';
10 |
11 | app.ALL_TODOS = 'all';
12 | app.ACTIVE_TODOS = 'active';
13 | app.COMPLETED_TODOS = 'completed';
14 | var TodoFooter = app.TodoFooter;
15 | var TodoItem = app.TodoItem;
16 |
17 | var ENTER_KEY = 13;
18 |
19 | var TodoApp = React.createClass({
20 | getInitialState: function () {
21 | return {
22 | nowShowing: app.ALL_TODOS,
23 | editing: null,
24 | newTodo: ''
25 | };
26 | },
27 |
28 | componentDidMount: function () {
29 | var setState = this.setState;
30 | var router = Router({
31 | '/': setState.bind(this, {nowShowing: app.ALL_TODOS}),
32 | '/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}),
33 | '/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS})
34 | });
35 | router.init('/');
36 | },
37 |
38 | handleChange: function (event) {
39 | this.setState({newTodo: event.target.value});
40 | },
41 |
42 | handleNewTodoKeyDown: function (event) {
43 | if (event.keyCode !== ENTER_KEY) {
44 | return;
45 | }
46 |
47 | event.preventDefault();
48 |
49 | var val = this.state.newTodo.trim();
50 |
51 | if (val) {
52 | this.props.model.addTodo(val);
53 | this.setState({newTodo: ''});
54 | }
55 | },
56 |
57 | toggleAll: function (event) {
58 | var checked = event.target.checked;
59 | this.props.model.toggleAll(checked);
60 | },
61 |
62 | toggle: function (todoToToggle) {
63 | this.props.model.toggle(todoToToggle);
64 | },
65 |
66 | destroy: function (todo) {
67 | this.props.model.destroy(todo);
68 | },
69 |
70 | edit: function (todo) {
71 | this.setState({editing: todo.id});
72 | },
73 |
74 | save: function (todoToSave, text) {
75 | this.props.model.save(todoToSave, text);
76 | this.setState({editing: null});
77 | },
78 |
79 | cancel: function () {
80 | this.setState({editing: null});
81 | },
82 |
83 | clearCompleted: function () {
84 | this.props.model.clearCompleted();
85 | },
86 |
87 | render: function () {
88 | var footer;
89 | var main;
90 | var todos = this.props.model.todos;
91 |
92 | var shownTodos = todos.filter(function (todo) {
93 | switch (this.state.nowShowing) {
94 | case app.ACTIVE_TODOS:
95 | return !todo.completed;
96 | case app.COMPLETED_TODOS:
97 | return todo.completed;
98 | default:
99 | return true;
100 | }
101 | }, this);
102 |
103 | var todoItems = shownTodos.map(function (todo) {
104 | return (
105 |
115 | );
116 | }, this);
117 |
118 | var activeTodoCount = todos.reduce(function (accum, todo) {
119 | return todo.completed ? accum : accum + 1;
120 | }, 0);
121 |
122 | var completedCount = todos.length - activeTodoCount;
123 |
124 | if (activeTodoCount || completedCount) {
125 | footer =
126 | ;
132 | }
133 |
134 | if (todos.length) {
135 | main = (
136 |
137 |
143 |
144 | {todoItems}
145 |
146 |
147 | );
148 | }
149 |
150 | return (
151 |
152 |
153 |
todos
154 |
162 |
163 | {main}
164 | {footer}
165 |
166 | );
167 | }
168 | });
169 |
170 | var model = new app.TodoModel('react-todos');
171 |
172 | function render() {
173 | ReactDOM.render(
174 | ,
175 | document.getElementsByClassName('todoapp')[0]
176 | );
177 | }
178 |
179 | model.subscribe(render);
180 | render();
181 | })();
182 |
--------------------------------------------------------------------------------
/todomvc/react/js/footer.js:
--------------------------------------------------------------------------------
1 | /*jshint quotmark:false */
2 | /*jshint white:false */
3 | /*jshint trailing:false */
4 | /*jshint newcap:false */
5 | /*global React */
6 | var app = app || {};
7 |
8 | (function () {
9 | 'use strict';
10 |
11 | app.TodoFooter = React.createClass({
12 | render: function () {
13 | var activeTodoWord = app.Utils.pluralize(this.props.count, 'item');
14 | var clearButton = null;
15 |
16 | if (this.props.completedCount > 0) {
17 | clearButton = (
18 | React.createElement('button', {
19 | className: "clear-completed",
20 | onClick: this.props.onClearCompleted}, [
21 | "Clear completed"
22 | ])
23 | );
24 | }
25 |
26 | var nowShowing = this.props.nowShowing;
27 | return (
28 | React.createElement('footer', {className: "footer"}, [
29 | React.createElement('span', {className: "todo-count"}, [
30 | React.createElement('strong', null, [this.props.count])," ", activeTodoWord, " left"
31 | ]),
32 | React.createElement('ul', {className: "filters"}, [
33 | React.createElement('li', null, [
34 | React.createElement('a', {
35 | href: "#/",
36 | className: classNames({selected: nowShowing === app.ALL_TODOS})}, [
37 | "All"
38 | ])
39 | ]),
40 | ' ',
41 | React.createElement('li', null, [
42 | React.createElement('a', {
43 | href: "#/active",
44 | className: classNames({selected: nowShowing === app.ACTIVE_TODOS})}, [
45 | "Active"
46 | ])
47 | ]),
48 | ' ',
49 | React.createElement('li', null, [
50 | React.createElement('a', {
51 | href: "#/completed",
52 | className: classNames({selected: nowShowing === app.COMPLETED_TODOS})}, [
53 | "Completed"
54 | ])
55 | ])
56 | ]),
57 | clearButton
58 | ])
59 | );
60 | }
61 | });
62 | })();
63 |
--------------------------------------------------------------------------------
/todomvc/react/js/footer.jsx:
--------------------------------------------------------------------------------
1 | /*jshint quotmark:false */
2 | /*jshint white:false */
3 | /*jshint trailing:false */
4 | /*jshint newcap:false */
5 | /*global React */
6 | var app = app || {};
7 |
8 | (function () {
9 | 'use strict';
10 |
11 | app.TodoFooter = React.createClass({
12 | render: function () {
13 | var activeTodoWord = app.Utils.pluralize(this.props.count, 'item');
14 | var clearButton = null;
15 |
16 | if (this.props.completedCount > 0) {
17 | clearButton = (
18 |
23 | );
24 | }
25 |
26 | var nowShowing = this.props.nowShowing;
27 | return (
28 |
59 | );
60 | }
61 | });
62 | })();
63 |
--------------------------------------------------------------------------------
/todomvc/react/js/todoItem.js:
--------------------------------------------------------------------------------
1 | /*jshint quotmark: false */
2 | /*jshint white: false */
3 | /*jshint trailing: false */
4 | /*jshint newcap: false */
5 | /*global React */
6 | var app = app || {};
7 |
8 | (function () {
9 | 'use strict';
10 |
11 | var ESCAPE_KEY = 27;
12 | var ENTER_KEY = 13;
13 |
14 | app.TodoItem = React.createClass({
15 | handleSubmit: function (event) {
16 | var val = this.state.editText.trim();
17 | if (val) {
18 | this.props.onSave(val);
19 | this.setState({editText: val});
20 | } else {
21 | this.props.onDestroy();
22 | }
23 | },
24 |
25 | handleEdit: function () {
26 | this.props.onEdit();
27 | this.setState({editText: this.props.todo.title});
28 | },
29 |
30 | handleKeyDown: function (event) {
31 | if (event.which === ESCAPE_KEY) {
32 | this.setState({editText: this.props.todo.title});
33 | this.props.onCancel(event);
34 | } else if (event.which === ENTER_KEY) {
35 | this.handleSubmit(event);
36 | }
37 | },
38 |
39 | handleChange: function (event) {
40 | if (this.props.editing) {
41 | this.setState({editText: event.target.value});
42 | }
43 | },
44 |
45 | getInitialState: function () {
46 | return {editText: this.props.todo.title};
47 | },
48 |
49 | /**
50 | * This is a completely optional performance enhancement that you can
51 | * implement on any React component. If you were to delete this method
52 | * the app would still work correctly (and still be very performant!), we
53 | * just use it as an example of how little code it takes to get an order
54 | * of magnitude performance improvement.
55 | */
56 | shouldComponentUpdate: function (nextProps, nextState) {
57 | return (
58 | nextProps.todo !== this.props.todo ||
59 | nextProps.editing !== this.props.editing ||
60 | nextState.editText !== this.state.editText
61 | );
62 | },
63 |
64 | /**
65 | * Safely manipulate the DOM after updating the state when invoking
66 | * `this.props.onEdit()` in the `handleEdit` method above.
67 | * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate
68 | * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate
69 | */
70 | componentDidUpdate: function (prevProps) {
71 | if (!prevProps.editing && this.props.editing) {
72 | var node = React.findDOMNode(this.refs.editField);
73 | node.focus();
74 | node.setSelectionRange(node.value.length, node.value.length);
75 | }
76 | },
77 |
78 | render: function () {
79 | return (
80 | React.createElement('li', {className: classNames({
81 | completed: this.props.todo.completed,
82 | editing: this.props.editing
83 | })}, [
84 | React.createElement('div', {className: "view"}, [
85 | React.createElement('input', {
86 | className: "toggle",
87 | type: "checkbox",
88 | checked: this.props.todo.completed,
89 | onChange: this.props.onToggle}
90 | ),
91 | React.createElement('label', {onDoubleClick: this.handleEdit}, [
92 | this.props.todo.title
93 | ]),
94 | React.createElement('button', {className: "destroy", onClick: this.props.onDestroy})
95 | ]),
96 | React.createElement('input', {
97 | ref: "editField",
98 | className: "edit",
99 | value: this.state.editText,
100 | onBlur: this.handleSubmit,
101 | onChange: this.handleChange,
102 | onKeyDown: this.handleKeyDown}
103 | )
104 | ])
105 | );
106 | }
107 | });
108 | })();
109 |
--------------------------------------------------------------------------------
/todomvc/react/js/todoItem.jsx:
--------------------------------------------------------------------------------
1 | /*jshint quotmark: false */
2 | /*jshint white: false */
3 | /*jshint trailing: false */
4 | /*jshint newcap: false */
5 | /*global React */
6 | var app = app || {};
7 |
8 | (function () {
9 | 'use strict';
10 |
11 | var ESCAPE_KEY = 27;
12 | var ENTER_KEY = 13;
13 |
14 | app.TodoItem = React.createClass({
15 | handleSubmit: function (event) {
16 | var val = this.state.editText.trim();
17 | if (val) {
18 | this.props.onSave(val);
19 | this.setState({editText: val});
20 | } else {
21 | this.props.onDestroy();
22 | }
23 | },
24 |
25 | handleEdit: function () {
26 | this.props.onEdit();
27 | this.setState({editText: this.props.todo.title});
28 | },
29 |
30 | handleKeyDown: function (event) {
31 | if (event.which === ESCAPE_KEY) {
32 | this.setState({editText: this.props.todo.title});
33 | this.props.onCancel(event);
34 | } else if (event.which === ENTER_KEY) {
35 | this.handleSubmit(event);
36 | }
37 | },
38 |
39 | handleChange: function (event) {
40 | if (this.props.editing) {
41 | this.setState({editText: event.target.value});
42 | }
43 | },
44 |
45 | getInitialState: function () {
46 | return {editText: this.props.todo.title};
47 | },
48 |
49 | /**
50 | * This is a completely optional performance enhancement that you can
51 | * implement on any React component. If you were to delete this method
52 | * the app would still work correctly (and still be very performant!), we
53 | * just use it as an example of how little code it takes to get an order
54 | * of magnitude performance improvement.
55 | */
56 | shouldComponentUpdate: function (nextProps, nextState) {
57 | return (
58 | nextProps.todo !== this.props.todo ||
59 | nextProps.editing !== this.props.editing ||
60 | nextState.editText !== this.state.editText
61 | );
62 | },
63 |
64 | /**
65 | * Safely manipulate the DOM after updating the state when invoking
66 | * `this.props.onEdit()` in the `handleEdit` method above.
67 | * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate
68 | * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate
69 | */
70 | componentDidUpdate: function (prevProps) {
71 | if (!prevProps.editing && this.props.editing) {
72 | var node = React.findDOMNode(this.refs.editField);
73 | node.focus();
74 | node.setSelectionRange(node.value.length, node.value.length);
75 | }
76 | },
77 |
78 | render: function () {
79 | return (
80 |
84 |
85 |
91 |
94 |
95 |
96 |
104 |
105 | );
106 | }
107 | });
108 | })();
109 |
--------------------------------------------------------------------------------
/todomvc/react/js/todoModel.js:
--------------------------------------------------------------------------------
1 | /*jshint quotmark:false */
2 | /*jshint white:false */
3 | /*jshint trailing:false */
4 | /*jshint newcap:false */
5 | var app = app || {};
6 |
7 | (function () {
8 | 'use strict';
9 |
10 | var Utils = app.Utils;
11 | // Generic "model" object. You can use whatever
12 | // framework you want. For this application it
13 | // may not even be worth separating this logic
14 | // out, but we do this to demonstrate one way to
15 | // separate out parts of your application.
16 | app.TodoModel = function (key) {
17 | this.key = key;
18 | this.todos = Utils.store(key);
19 | this.onChanges = [];
20 | };
21 |
22 | app.TodoModel.prototype.subscribe = function (onChange) {
23 | this.onChanges.push(onChange);
24 | };
25 |
26 | app.TodoModel.prototype.inform = function () {
27 | Utils.store(this.key, this.todos);
28 | this.onChanges.forEach(function (cb) { cb(); });
29 | };
30 |
31 | app.TodoModel.prototype.addTodo = function (title) {
32 | this.todos = this.todos.concat({
33 | id: Utils.uuid(),
34 | title: title,
35 | completed: false
36 | });
37 |
38 | this.inform();
39 | };
40 |
41 | app.TodoModel.prototype.toggleAll = function (checked) {
42 | // Note: it's usually better to use immutable data structures since they're
43 | // easier to reason about and React works very well with them. That's why
44 | // we use map() and filter() everywhere instead of mutating the array or
45 | // todo items themselves.
46 | this.todos = this.todos.map(function (todo) {
47 | return Utils.extend({}, todo, {completed: checked});
48 | });
49 |
50 | this.inform();
51 | };
52 |
53 | app.TodoModel.prototype.toggle = function (todoToToggle) {
54 | this.todos = this.todos.map(function (todo) {
55 | return todo !== todoToToggle ?
56 | todo :
57 | Utils.extend({}, todo, {completed: !todo.completed});
58 | });
59 |
60 | this.inform();
61 | };
62 |
63 | app.TodoModel.prototype.destroy = function (todo) {
64 | this.todos = this.todos.filter(function (candidate) {
65 | return candidate !== todo;
66 | });
67 |
68 | this.inform();
69 | };
70 |
71 | app.TodoModel.prototype.save = function (todoToSave, text) {
72 | this.todos = this.todos.map(function (todo) {
73 | return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text});
74 | });
75 |
76 | this.inform();
77 | };
78 |
79 | app.TodoModel.prototype.clearCompleted = function () {
80 | this.todos = this.todos.filter(function (todo) {
81 | return !todo.completed;
82 | });
83 |
84 | this.inform();
85 | };
86 |
87 | })();
88 |
--------------------------------------------------------------------------------
/todomvc/react/js/utils.js:
--------------------------------------------------------------------------------
1 | var app = app || {};
2 |
3 | (function () {
4 | 'use strict';
5 |
6 | app.Utils = {
7 | uuid: function () {
8 | /*jshint bitwise:false */
9 | var i, random;
10 | var uuid = '';
11 |
12 | for (i = 0; i < 32; i++) {
13 | random = Math.random() * 16 | 0;
14 | if (i === 8 || i === 12 || i === 16 || i === 20) {
15 | uuid += '-';
16 | }
17 | uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
18 | .toString(16);
19 | }
20 |
21 | return uuid;
22 | },
23 |
24 | pluralize: function (count, word) {
25 | return count === 1 ? word : word + 's';
26 | },
27 |
28 | store: function (namespace, data) {
29 | if (data) {
30 | return localStorage.setItem(namespace, JSON.stringify(data));
31 | }
32 |
33 | var store = localStorage.getItem(namespace);
34 | return (store && JSON.parse(store)) || [];
35 | },
36 |
37 | extend: function () {
38 | var newObj = {};
39 | for (var i = 0; i < arguments.length; i++) {
40 | var obj = arguments[i];
41 | for (var key in obj) {
42 | if (obj.hasOwnProperty(key)) {
43 | newObj[key] = obj[key];
44 | }
45 | }
46 | }
47 | return newObj;
48 | }
49 | };
50 | })();
51 |
--------------------------------------------------------------------------------
/todomvc/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "dependencies": {
4 | "classnames": "^2.1.5",
5 | "director": "^1.2.0",
6 | "react": "^15.0.0",
7 | "react-dom": "^15.0.2",
8 | "todomvc-app-css": "^2.0.0",
9 | "todomvc-common": "^1.0.1"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/todomvc/react/readme.md:
--------------------------------------------------------------------------------
1 | # React TodoMVC Example
2 |
3 | > React is a JavaScript library for creating user interfaces. Its core principles are declarative code, efficiency, and flexibility. Simply specify what your component looks like and React will keep it up-to-date when the underlying data changes.
4 |
5 | > _[React - facebook.github.io/react](http://facebook.github.io/react)_
6 |
7 |
8 | ## Learning React
9 |
10 | The [React getting started documentation](http://facebook.github.io/react/docs/getting-started.html) is a great way to get started.
11 |
12 | Here are some links you may find helpful:
13 |
14 | * [Documentation](http://facebook.github.io/react/docs/getting-started.html)
15 | * [API Reference](http://facebook.github.io/react/docs/reference.html)
16 | * [Blog](http://facebook.github.io/react/blog/)
17 | * [React on GitHub](https://github.com/facebook/react)
18 | * [Support](http://facebook.github.io/react/support.html)
19 |
20 | Articles and guides from the community:
21 |
22 | * [How is Facebook's React JavaScript library](http://www.quora.com/React-JS-Library/How-is-Facebooks-React-JavaScript-library)
23 | * [React: Under the hood](http://www.quora.com/Pete-Hunt/Posts/React-Under-the-Hood)
24 |
25 | Get help from other React users:
26 |
27 | * [React on StackOverflow](http://stackoverflow.com/questions/tagged/reactjs)
28 | * [Discussion Forum](https://discuss.reactjs.org/)
29 |
30 | _If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
31 |
32 |
33 | ## Running
34 |
35 | The app is built with [JSX](http://facebook.github.io/react/docs/jsx-in-depth.html) and compiled at runtime for a lighter and more fun code reading experience. As stated in the link, JSX is not mandatory.
36 |
37 | To run the app, spin up an HTTP server (e.g. `python -m SimpleHTTPServer`) and visit http://localhost/.../myexample/.
38 |
--------------------------------------------------------------------------------
/todomvc/react/todomvc-app-css/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "_args": [
3 | [
4 | "todomvc-app-css@^2.0.0",
5 | "/Users/lorenzo/Projects/todomvc-perf-comparison/todomvc/react"
6 | ]
7 | ],
8 | "_from": "todomvc-app-css@>=2.0.0 <3.0.0",
9 | "_id": "todomvc-app-css@2.0.4",
10 | "_inCache": true,
11 | "_installable": true,
12 | "_location": "/todomvc-app-css",
13 | "_nodeVersion": "4.2.4",
14 | "_npmUser": {
15 | "email": "sindresorhus@gmail.com",
16 | "name": "sindresorhus"
17 | },
18 | "_npmVersion": "2.14.12",
19 | "_phantomChildren": {},
20 | "_requested": {
21 | "name": "todomvc-app-css",
22 | "raw": "todomvc-app-css@^2.0.0",
23 | "rawSpec": "^2.0.0",
24 | "scope": null,
25 | "spec": ">=2.0.0 <3.0.0",
26 | "type": "range"
27 | },
28 | "_requiredBy": [
29 | "/"
30 | ],
31 | "_resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.4.tgz",
32 | "_shasum": "a1b62664d1e3ade62140b6c40a5bb92adea0f0ff",
33 | "_shrinkwrap": null,
34 | "_spec": "todomvc-app-css@^2.0.0",
35 | "_where": "/Users/lorenzo/Projects/todomvc-perf-comparison/todomvc/react",
36 | "author": {
37 | "email": "sindresorhus@gmail.com",
38 | "name": "Sindre Sorhus",
39 | "url": "sindresorhus.com"
40 | },
41 | "bugs": {
42 | "url": "https://github.com/tastejs/todomvc-app-css/issues"
43 | },
44 | "dependencies": {},
45 | "description": "CSS for TodoMVC apps",
46 | "devDependencies": {},
47 | "directories": {},
48 | "dist": {
49 | "shasum": "a1b62664d1e3ade62140b6c40a5bb92adea0f0ff",
50 | "tarball": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.4.tgz"
51 | },
52 | "files": [
53 | "index.css"
54 | ],
55 | "gitHead": "47337a0da5727cbca2672c3055411405bd43bde4",
56 | "homepage": "https://github.com/tastejs/todomvc-app-css",
57 | "keywords": [
58 | "app",
59 | "css",
60 | "style",
61 | "stylesheet",
62 | "tastejs",
63 | "template",
64 | "todo",
65 | "todomvc"
66 | ],
67 | "license": "CC-BY-4.0",
68 | "maintainers": [
69 | {
70 | "name": "sindresorhus",
71 | "email": "sindresorhus@gmail.com"
72 | },
73 | {
74 | "name": "addyosmani",
75 | "email": "addyosmani@gmail.com"
76 | },
77 | {
78 | "name": "passy",
79 | "email": "phartig@rdrei.net"
80 | },
81 | {
82 | "name": "stephenplusplus",
83 | "email": "sawchuk@gmail.com"
84 | }
85 | ],
86 | "name": "todomvc-app-css",
87 | "optionalDependencies": {},
88 | "readme": "ERROR: No README data found!",
89 | "repository": {
90 | "type": "git",
91 | "url": "git+https://github.com/tastejs/todomvc-app-css.git"
92 | },
93 | "scripts": {},
94 | "style": "index.css",
95 | "version": "2.0.4"
96 | }
97 |
--------------------------------------------------------------------------------
/todomvc/react/todomvc-app-css/readme.md:
--------------------------------------------------------------------------------
1 | # todomvc-app-css
2 |
3 | > CSS for TodoMVC apps
4 |
5 | 
6 |
7 |
8 | ## Install
9 |
10 |
11 | ```
12 | $ npm install --save todomvc-app-css
13 | ```
14 |
15 |
16 | ## Getting started
17 |
18 | ```html
19 |
20 | ```
21 |
22 | See the [TodoMVC app template](https://github.com/tastejs/todomvc-app-template).
23 |
24 |
25 |
26 | ## License
27 |
28 | This work by Sindre Sorhus is licensed under a Creative Commons Attribution 4.0 International License.
29 |
--------------------------------------------------------------------------------
/todomvc/react/todomvc-common/base.css:
--------------------------------------------------------------------------------
1 | hr {
2 | margin: 20px 0;
3 | border: 0;
4 | border-top: 1px dashed #c5c5c5;
5 | border-bottom: 1px dashed #f7f7f7;
6 | }
7 |
8 | .learn a {
9 | font-weight: normal;
10 | text-decoration: none;
11 | color: #b83f45;
12 | }
13 |
14 | .learn a:hover {
15 | text-decoration: underline;
16 | color: #787e7e;
17 | }
18 |
19 | .learn h3,
20 | .learn h4,
21 | .learn h5 {
22 | margin: 10px 0;
23 | font-weight: 500;
24 | line-height: 1.2;
25 | color: #000;
26 | }
27 |
28 | .learn h3 {
29 | font-size: 24px;
30 | }
31 |
32 | .learn h4 {
33 | font-size: 18px;
34 | }
35 |
36 | .learn h5 {
37 | margin-bottom: 0;
38 | font-size: 14px;
39 | }
40 |
41 | .learn ul {
42 | padding: 0;
43 | margin: 0 0 30px 25px;
44 | }
45 |
46 | .learn li {
47 | line-height: 20px;
48 | }
49 |
50 | .learn p {
51 | font-size: 15px;
52 | font-weight: 300;
53 | line-height: 1.3;
54 | margin-top: 0;
55 | margin-bottom: 0;
56 | }
57 |
58 | #issue-count {
59 | display: none;
60 | }
61 |
62 | .quote {
63 | border: none;
64 | margin: 20px 0 60px 0;
65 | }
66 |
67 | .quote p {
68 | font-style: italic;
69 | }
70 |
71 | .quote p:before {
72 | content: '“';
73 | font-size: 50px;
74 | opacity: .15;
75 | position: absolute;
76 | top: -20px;
77 | left: 3px;
78 | }
79 |
80 | .quote p:after {
81 | content: '”';
82 | font-size: 50px;
83 | opacity: .15;
84 | position: absolute;
85 | bottom: -42px;
86 | right: 3px;
87 | }
88 |
89 | .quote footer {
90 | position: absolute;
91 | bottom: -40px;
92 | right: 0;
93 | }
94 |
95 | .quote footer img {
96 | border-radius: 3px;
97 | }
98 |
99 | .quote footer a {
100 | margin-left: 5px;
101 | vertical-align: middle;
102 | }
103 |
104 | .speech-bubble {
105 | position: relative;
106 | padding: 10px;
107 | background: rgba(0, 0, 0, .04);
108 | border-radius: 5px;
109 | }
110 |
111 | .speech-bubble:after {
112 | content: '';
113 | position: absolute;
114 | top: 100%;
115 | right: 30px;
116 | border: 13px solid transparent;
117 | border-top-color: rgba(0, 0, 0, .04);
118 | }
119 |
120 | .learn-bar > .learn {
121 | position: absolute;
122 | width: 272px;
123 | top: 8px;
124 | left: -300px;
125 | padding: 10px;
126 | border-radius: 5px;
127 | background-color: rgba(255, 255, 255, .6);
128 | transition-property: left;
129 | transition-duration: 500ms;
130 | }
131 |
132 | @media (min-width: 899px) {
133 | .learn-bar {
134 | width: auto;
135 | padding-left: 300px;
136 | }
137 |
138 | .learn-bar > .learn {
139 | left: 8px;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/.bin
2 | node_modules/babel-core
3 | node_modules/babel-preset-es2015
4 | node_modules/browserify
5 |
6 | node_modules/todomvc-app-css/*
7 | !node_modules/todomvc-app-css/index.css
8 |
9 | node_modules/todomvc-common/*
10 | !node_modules/todomvc-common/base.css
11 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/README.md:
--------------------------------------------------------------------------------
1 | # Vanilla ES6 (ES2015) • [TodoMVC](http://todomvc.com)
2 |
3 | > An exact port of the [Vanilla JS Example](http://todomvc.com/examples/vanillajs/), but translated into ES6, also known as ES2015.
4 |
5 | ## Learning ES6
6 |
7 | - [ES6 Features](https://github.com/lukehoban/es6features)
8 | - [Learning Resources](https://github.com/ericdouglas/ES6-Learning)
9 | - [Babel's ES6 Guide](https://babeljs.io/docs/learn-es2015/)
10 | - [Babel Compiler](https://babeljs.io/)
11 |
12 | ## Installation
13 |
14 | To get started with this example, navigate into the example folder and install the NPM modules.
15 | ```bash
16 | cd todomvc/examples/vanilla-es6
17 | npm install
18 | ```
19 |
20 | ## Compiling ES6 to ES5
21 |
22 | After NPM modules have been installed, use the pre-defined Babel script to convert the `src` files. Browserify is also used so that `module.exports` and `require()` can be run in your browser.
23 |
24 | ```bash
25 | npm run compile
26 | ```
27 |
28 | ## Support
29 |
30 | - [Twitter](http://twitter.com/lukeed05)
31 |
32 | *Let us [know](https://github.com/tastejs/todomvc/issues) if you discover anything worth sharing.*
33 |
34 |
35 | ## Implementation
36 |
37 | Uses [Babel JS](https://babeljs.io/) to compile ES6 code to ES5, which is then readable by all browsers.
38 |
39 |
40 | ## Credit
41 |
42 | Created by [Luke Edwards](http://www.lukeed.com)
43 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/dist/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _controller = require('./controller');
4 |
5 | var _controller2 = _interopRequireDefault(_controller);
6 |
7 | var _helpers = require('./helpers');
8 |
9 | var helpers = _interopRequireWildcard(_helpers);
10 |
11 | var _template = require('./template');
12 |
13 | var _template2 = _interopRequireDefault(_template);
14 |
15 | var _store = require('./store');
16 |
17 | var _store2 = _interopRequireDefault(_store);
18 |
19 | var _model = require('./model');
20 |
21 | var _model2 = _interopRequireDefault(_model);
22 |
23 | var _view = require('./view');
24 |
25 | var _view2 = _interopRequireDefault(_view);
26 |
27 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
28 |
29 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
30 |
31 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
32 |
33 | var $on = helpers.$on;
34 | var setView = function setView() {
35 | return todo.controller.setView(document.location.hash);
36 | };
37 |
38 | var Todo =
39 | /**
40 | * Init new Todo List
41 | * @param {string} The name of your list
42 | */
43 | function Todo(name) {
44 | _classCallCheck(this, Todo);
45 |
46 | this.storage = new _store2.default(name);
47 | this.model = new _model2.default(this.storage);
48 |
49 | this.template = new _template2.default();
50 | this.view = new _view2.default(this.template);
51 |
52 | this.controller = new _controller2.default(this.model, this.view);
53 | };
54 |
55 | var todo = new Todo('todos-vanillajs');
56 |
57 | $on(window, 'load', setView);
58 | $on(window, 'hashchange', setView);
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/dist/helpers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.qs = qs;
7 | exports.qsa = qsa;
8 | exports.$on = $on;
9 | exports.$delegate = $delegate;
10 | exports.$parent = $parent;
11 | // Allow for looping on nodes by chaining:
12 | // qsa('.foo').forEach(function () {})
13 | NodeList.prototype.forEach = Array.prototype.forEach;
14 |
15 | // Get element(s) by CSS selector:
16 | function qs(selector, scope) {
17 | return (scope || document).querySelector(selector);
18 | }
19 |
20 | function qsa(selector, scope) {
21 | return (scope || document).querySelectorAll(selector);
22 | }
23 |
24 | // addEventListener wrapper:
25 | function $on(target, type, callback, useCapture) {
26 | target.addEventListener(type, callback, !!useCapture);
27 | }
28 |
29 | // Attach a handler to event for all elements that match the selector,
30 | // now or in the future, based on a root element
31 | function $delegate(target, selector, type, handler) {
32 | var dispatchEvent = function dispatchEvent(event) {
33 | var targetElement = event.target;
34 | var potentialElements = qsa(selector, target);
35 | var hasMatch = Array.from(potentialElements).includes(targetElement);
36 |
37 | if (hasMatch) {
38 | handler.call(targetElement, event);
39 | }
40 | };
41 |
42 | // https://developer.mozilla.org/en-US/docs/Web/Events/blur
43 | var useCapture = type === 'blur' || type === 'focus';
44 |
45 | $on(target, type, dispatchEvent, useCapture);
46 | }
47 |
48 | // Find the element's parent with the given tag name:
49 | // $parent(qs('a'), 'div')
50 | function $parent(element, tagName) {
51 | if (!element.parentNode) {
52 | return;
53 | }
54 |
55 | if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) {
56 | return element.parentNode;
57 | }
58 |
59 | return $parent(element.parentNode, tagName);
60 | }
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/dist/store.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 |
9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
10 |
11 | /*jshint eqeqeq:false */
12 |
13 | /**
14 | * Creates a new client side storage object and will create an empty
15 | * collection if no collection already exists.
16 | *
17 | * @param {string} name The name of our DB we want to use
18 | * @param {function} callback Our fake DB uses callbacks because in
19 | * real life you probably would be making AJAX calls
20 | */
21 |
22 | var Store = function () {
23 | function Store(name, callback) {
24 | _classCallCheck(this, Store);
25 |
26 | this._dbName = name;
27 |
28 | if (!localStorage[name]) {
29 | var data = {
30 | todos: []
31 | };
32 |
33 | localStorage[name] = JSON.stringify(data);
34 | }
35 |
36 | if (callback) {
37 | callback.call(this, JSON.parse(localStorage[name]));
38 | }
39 | }
40 |
41 | /**
42 | * Finds items based on a query given as a JS object
43 | *
44 | * @param {object} query The query to match against (i.e. {foo: 'bar'})
45 | * @param {function} callback The callback to fire when the query has
46 | * completed running
47 | *
48 | * @example
49 | * db.find({foo: 'bar', hello: 'world'}, function (data) {
50 | * // data will return any items that have foo: bar and
51 | * // hello: world in their properties
52 | * })
53 | */
54 |
55 | _createClass(Store, [{
56 | key: "find",
57 | value: function find(query, callback) {
58 | var todos = JSON.parse(localStorage[this._dbName]).todos;
59 |
60 | callback.call(this, todos.filter(function (todo) {
61 | for (var q in query) {
62 | if (query[q] !== todo[q]) {
63 | return false;
64 | }
65 | }
66 | return true;
67 | }));
68 | }
69 |
70 | /**
71 | * Will retrieve all data from the collection
72 | *
73 | * @param {function} callback The callback to fire upon retrieving data
74 | */
75 |
76 | }, {
77 | key: "findAll",
78 | value: function findAll(callback) {
79 | if (callback) {
80 | callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
81 | }
82 | }
83 |
84 | /**
85 | * Will save the given data to the DB. If no item exists it will create a new
86 | * item, otherwise it'll simply update an existing item's properties
87 | *
88 | * @param {object} updateData The data to save back into the DB
89 | * @param {function} callback The callback to fire after saving
90 | * @param {number} id An optional param to enter an ID of an item to update
91 | */
92 |
93 | }, {
94 | key: "save",
95 | value: function save(updateData, callback, id) {
96 | var data = JSON.parse(localStorage[this._dbName]);
97 | var todos = data.todos;
98 | var len = todos.length;
99 |
100 | // If an ID was actually given, find the item and update each property
101 | if (id) {
102 | for (var i = 0; i < len; i++) {
103 | if (todos[i].id === id) {
104 | for (var key in updateData) {
105 | todos[i][key] = updateData[key];
106 | }
107 | break;
108 | }
109 | }
110 |
111 | localStorage[this._dbName] = JSON.stringify(data);
112 |
113 | if (callback) {
114 | callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
115 | }
116 | } else {
117 | // Generate an ID
118 | updateData.id = new Date().getTime();
119 |
120 | todos.push(updateData);
121 | localStorage[this._dbName] = JSON.stringify(data);
122 |
123 | if (callback) {
124 | callback.call(this, [updateData]);
125 | }
126 | }
127 | }
128 |
129 | /**
130 | * Will remove an item from the Store based on its ID
131 | *
132 | * @param {number} id The ID of the item you want to remove
133 | * @param {function} callback The callback to fire after saving
134 | */
135 |
136 | }, {
137 | key: "remove",
138 | value: function remove(id, callback) {
139 | var data = JSON.parse(localStorage[this._dbName]);
140 | var todos = data.todos;
141 | var len = todos.length;
142 |
143 | for (var i = 0; i < todos.length; i++) {
144 | if (todos[i].id == id) {
145 | todos.splice(i, 1);
146 | break;
147 | }
148 | }
149 |
150 | localStorage[this._dbName] = JSON.stringify(data);
151 |
152 | if (callback) {
153 | callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
154 | }
155 | }
156 |
157 | /**
158 | * Will drop all storage and start fresh
159 | *
160 | * @param {function} callback The callback to fire after dropping the data
161 | */
162 |
163 | }, {
164 | key: "drop",
165 | value: function drop(callback) {
166 | localStorage[this._dbName] = JSON.stringify({ todos: [] });
167 |
168 | if (callback) {
169 | callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
170 | }
171 | }
172 | }]);
173 |
174 | return Store;
175 | }();
176 |
177 | exports.default = Store;
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/dist/template.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 |
9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
10 |
11 | var htmlEscapes = {
12 | '&': '&',
13 | '<': '<',
14 | '>': '>',
15 | '"': '"',
16 | '\'': ''',
17 | '`': '`'
18 | };
19 |
20 | var reUnescapedHtml = /[&<>"'`]/g;
21 | var reHasUnescapedHtml = new RegExp(reUnescapedHtml.source);
22 |
23 | var escape = function escape(str) {
24 | return str && reHasUnescapedHtml.test(str) ? str.replace(reUnescapedHtml, escapeHtmlChar) : str;
25 | };
26 | var escapeHtmlChar = function escapeHtmlChar(chr) {
27 | return htmlEscapes[chr];
28 | };
29 |
30 | var Template = function () {
31 | function Template() {
32 | _classCallCheck(this, Template);
33 |
34 | this.defaultTemplate = '\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t';
35 | }
36 |
37 | /**
38 | * Creates an
HTML string and returns it for placement in your app.
39 | *
40 | * NOTE: In real life you should be using a templating engine such as Mustache
41 | * or Handlebars, however, this is a vanilla JS example.
42 | *
43 | * @param {object} data The object containing keys you want to find in the
44 | * template to replace.
45 | * @returns {string} HTML String of an
20 |
21 |
22 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/node_modules/todomvc-common/base.css:
--------------------------------------------------------------------------------
1 | hr {
2 | margin: 20px 0;
3 | border: 0;
4 | border-top: 1px dashed #c5c5c5;
5 | border-bottom: 1px dashed #f7f7f7;
6 | }
7 |
8 | .learn a {
9 | font-weight: normal;
10 | text-decoration: none;
11 | color: #b83f45;
12 | }
13 |
14 | .learn a:hover {
15 | text-decoration: underline;
16 | color: #787e7e;
17 | }
18 |
19 | .learn h3,
20 | .learn h4,
21 | .learn h5 {
22 | margin: 10px 0;
23 | font-weight: 500;
24 | line-height: 1.2;
25 | color: #000;
26 | }
27 |
28 | .learn h3 {
29 | font-size: 24px;
30 | }
31 |
32 | .learn h4 {
33 | font-size: 18px;
34 | }
35 |
36 | .learn h5 {
37 | margin-bottom: 0;
38 | font-size: 14px;
39 | }
40 |
41 | .learn ul {
42 | padding: 0;
43 | margin: 0 0 30px 25px;
44 | }
45 |
46 | .learn li {
47 | line-height: 20px;
48 | }
49 |
50 | .learn p {
51 | font-size: 15px;
52 | font-weight: 300;
53 | line-height: 1.3;
54 | margin-top: 0;
55 | margin-bottom: 0;
56 | }
57 |
58 | #issue-count {
59 | display: none;
60 | }
61 |
62 | .quote {
63 | border: none;
64 | margin: 20px 0 60px 0;
65 | }
66 |
67 | .quote p {
68 | font-style: italic;
69 | }
70 |
71 | .quote p:before {
72 | content: '“';
73 | font-size: 50px;
74 | opacity: .15;
75 | position: absolute;
76 | top: -20px;
77 | left: 3px;
78 | }
79 |
80 | .quote p:after {
81 | content: '”';
82 | font-size: 50px;
83 | opacity: .15;
84 | position: absolute;
85 | bottom: -42px;
86 | right: 3px;
87 | }
88 |
89 | .quote footer {
90 | position: absolute;
91 | bottom: -40px;
92 | right: 0;
93 | }
94 |
95 | .quote footer img {
96 | border-radius: 3px;
97 | }
98 |
99 | .quote footer a {
100 | margin-left: 5px;
101 | vertical-align: middle;
102 | }
103 |
104 | .speech-bubble {
105 | position: relative;
106 | padding: 10px;
107 | background: rgba(0, 0, 0, .04);
108 | border-radius: 5px;
109 | }
110 |
111 | .speech-bubble:after {
112 | content: '';
113 | position: absolute;
114 | top: 100%;
115 | right: 30px;
116 | border: 13px solid transparent;
117 | border-top-color: rgba(0, 0, 0, .04);
118 | }
119 |
120 | .learn-bar > .learn {
121 | position: absolute;
122 | width: 272px;
123 | top: 8px;
124 | left: -300px;
125 | padding: 10px;
126 | border-radius: 5px;
127 | background-color: rgba(255, 255, 255, .6);
128 | transition-property: left;
129 | transition-duration: 500ms;
130 | }
131 |
132 | @media (min-width: 899px) {
133 | .learn-bar {
134 | width: auto;
135 | padding-left: 300px;
136 | }
137 |
138 | .learn-bar > .learn {
139 | left: 8px;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "compile": "babel src --presets es2015 --out-dir=dist && browserify dist/app.js > dist/bundle.js",
5 | "prepublish": "npm run compile"
6 | },
7 | "dependencies": {
8 | "todomvc-app-css": "^2.0.1",
9 | "todomvc-common": "^1.0.2"
10 | },
11 | "devDependencies": {
12 | "babel-core": "^6.1.0",
13 | "babel-preset-es2015": "^6.1.18",
14 | "browserify": "^12.0.1"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/src/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esnext": true
3 | }
4 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/src/app.js:
--------------------------------------------------------------------------------
1 | import Controller from './controller';
2 | import * as helpers from './helpers';
3 | import Template from './template';
4 | import Store from './store';
5 | import Model from './model';
6 | import View from './view';
7 |
8 | const $on = helpers.$on;
9 | const setView = () => todo.controller.setView(document.location.hash);
10 |
11 | class Todo {
12 | /**
13 | * Init new Todo List
14 | * @param {string} The name of your list
15 | */
16 | constructor(name) {
17 | this.storage = new Store(name);
18 | this.model = new Model(this.storage);
19 |
20 | this.template = new Template();
21 | this.view = new View(this.template);
22 |
23 | this.controller = new Controller(this.model, this.view);
24 | }
25 | }
26 |
27 | const todo = new Todo('todos-vanillajs');
28 |
29 | $on(window, 'load', setView);
30 | $on(window, 'hashchange', setView);
31 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/src/helpers.js:
--------------------------------------------------------------------------------
1 | // Allow for looping on nodes by chaining:
2 | // qsa('.foo').forEach(function () {})
3 | NodeList.prototype.forEach = Array.prototype.forEach;
4 |
5 | // Get element(s) by CSS selector:
6 | export function qs(selector, scope) {
7 | return (scope || document).querySelector(selector);
8 | }
9 |
10 | export function qsa(selector, scope) {
11 | return (scope || document).querySelectorAll(selector);
12 | }
13 |
14 | // addEventListener wrapper:
15 | export function $on(target, type, callback, useCapture) {
16 | target.addEventListener(type, callback, !!useCapture);
17 | }
18 |
19 | // Attach a handler to event for all elements that match the selector,
20 | // now or in the future, based on a root element
21 | export function $delegate(target, selector, type, handler) {
22 | const dispatchEvent = event => {
23 | const targetElement = event.target;
24 | const potentialElements = qsa(selector, target);
25 | const hasMatch = Array.from(potentialElements).includes(targetElement);
26 |
27 | if (hasMatch) {
28 | handler.call(targetElement, event);
29 | }
30 | };
31 |
32 | // https://developer.mozilla.org/en-US/docs/Web/Events/blur
33 | const useCapture = type === 'blur' || type === 'focus';
34 |
35 | $on(target, type, dispatchEvent, useCapture);
36 | }
37 |
38 | // Find the element's parent with the given tag name:
39 | // $parent(qs('a'), 'div')
40 | export function $parent(element, tagName) {
41 | if (!element.parentNode) {
42 | return;
43 | }
44 |
45 | if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) {
46 | return element.parentNode;
47 | }
48 |
49 | return $parent(element.parentNode, tagName);
50 | }
51 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/src/model.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates a new Model instance and hooks up the storage.
3 | * @constructor
4 | * @param {object} storage A reference to the client side storage class
5 | */
6 | export default class Model {
7 | constructor(storage) {
8 | this.storage = storage;
9 | }
10 |
11 | /**
12 | * Creates a new todo model
13 | *
14 | * @param {string} [title] The title of the task
15 | * @param {function} [callback] The callback to fire after the model is created
16 | */
17 | create(title, callback){
18 | title = title || '';
19 |
20 | const newItem = {
21 | title: title.trim(),
22 | completed: false
23 | };
24 |
25 | this.storage.save(newItem, callback);
26 | }
27 |
28 | /**
29 | * Finds and returns a model in storage. If no query is given it'll simply
30 | * return everything. If you pass in a string or number it'll look that up as
31 | * the ID of the model to find. Lastly, you can pass it an object to match
32 | * against.
33 | *
34 | * @param {string|number|object} [query] A query to match models against
35 | * @param {function} [callback] The callback to fire after the model is found
36 | *
37 | * @example
38 | * model.read(1, func) // Will find the model with an ID of 1
39 | * model.read('1') // Same as above
40 | * //Below will find a model with foo equalling bar and hello equalling world.
41 | * model.read({ foo: 'bar', hello: 'world' })
42 | */
43 | read(query, callback){
44 | const queryType = typeof query;
45 |
46 | if (queryType === 'function') {
47 | this.storage.findAll(query);
48 | } else if (queryType === 'string' || queryType === 'number') {
49 | query = parseInt(query, 10);
50 | this.storage.find({id: query}, callback);
51 | } else {
52 | this.storage.find(query, callback);
53 | }
54 | }
55 |
56 | /**
57 | * Updates a model by giving it an ID, data to update, and a callback to fire when
58 | * the update is complete.
59 | *
60 | * @param {number} id The id of the model to update
61 | * @param {object} data The properties to update and their new value
62 | * @param {function} callback The callback to fire when the update is complete.
63 | */
64 | update(id, data, callback){
65 | this.storage.save(data, callback, id);
66 | }
67 |
68 | /**
69 | * Removes a model from storage
70 | *
71 | * @param {number} id The ID of the model to remove
72 | * @param {function} callback The callback to fire when the removal is complete.
73 | */
74 | remove(id, callback){
75 | this.storage.remove(id, callback);
76 | }
77 |
78 | /**
79 | * WARNING: Will remove ALL data from storage.
80 | *
81 | * @param {function} callback The callback to fire when the storage is wiped.
82 | */
83 | removeAll(callback){
84 | this.storage.drop(callback);
85 | }
86 |
87 | /**
88 | * Returns a count of all todos
89 | */
90 | getCount(callback){
91 | const todos = {
92 | active: 0,
93 | completed: 0,
94 | total: 0
95 | };
96 |
97 | this.storage.findAll(data => {
98 | for (let todo of data) {
99 | if (todo.completed) {
100 | todos.completed++;
101 | } else {
102 | todos.active++;
103 | }
104 |
105 | todos.total++;
106 | }
107 |
108 | callback(todos);
109 | });
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/src/store.js:
--------------------------------------------------------------------------------
1 | /*jshint eqeqeq:false */
2 |
3 | /**
4 | * Creates a new client side storage object and will create an empty
5 | * collection if no collection already exists.
6 | *
7 | * @param {string} name The name of our DB we want to use
8 | * @param {function} callback Our fake DB uses callbacks because in
9 | * real life you probably would be making AJAX calls
10 | */
11 | export default class Store {
12 | constructor(name, callback) {
13 | this._dbName = name;
14 |
15 | if (!localStorage[name]) {
16 | const data = {
17 | todos: []
18 | };
19 |
20 | localStorage[name] = JSON.stringify(data);
21 | }
22 |
23 | if (callback) {
24 | callback.call(this, JSON.parse(localStorage[name]));
25 | }
26 | }
27 |
28 | /**
29 | * Finds items based on a query given as a JS object
30 | *
31 | * @param {object} query The query to match against (i.e. {foo: 'bar'})
32 | * @param {function} callback The callback to fire when the query has
33 | * completed running
34 | *
35 | * @example
36 | * db.find({foo: 'bar', hello: 'world'}, function (data) {
37 | * // data will return any items that have foo: bar and
38 | * // hello: world in their properties
39 | * })
40 | */
41 | find(query, callback){
42 | const todos = JSON.parse(localStorage[this._dbName]).todos;
43 |
44 | callback.call(this, todos.filter(todo => {
45 | for (let q in query) {
46 | if (query[q] !== todo[q]) {
47 | return false;
48 | }
49 | }
50 | return true;
51 | }));
52 | }
53 |
54 | /**
55 | * Will retrieve all data from the collection
56 | *
57 | * @param {function} callback The callback to fire upon retrieving data
58 | */
59 | findAll(callback){
60 | if (callback) {
61 | callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
62 | }
63 | }
64 |
65 | /**
66 | * Will save the given data to the DB. If no item exists it will create a new
67 | * item, otherwise it'll simply update an existing item's properties
68 | *
69 | * @param {object} updateData The data to save back into the DB
70 | * @param {function} callback The callback to fire after saving
71 | * @param {number} id An optional param to enter an ID of an item to update
72 | */
73 | save(updateData, callback, id){
74 | const data = JSON.parse(localStorage[this._dbName]);
75 | const todos = data.todos;
76 | const len = todos.length;
77 |
78 | // If an ID was actually given, find the item and update each property
79 | if (id) {
80 | for (let i = 0; i < len; i++) {
81 | if (todos[i].id === id) {
82 | for (let key in updateData) {
83 | todos[i][key] = updateData[key];
84 | }
85 | break;
86 | }
87 | }
88 |
89 | localStorage[this._dbName] = JSON.stringify(data);
90 |
91 | if (callback) {
92 | callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
93 | }
94 | } else {
95 | // Generate an ID
96 | updateData.id = new Date().getTime();
97 |
98 | todos.push(updateData);
99 | localStorage[this._dbName] = JSON.stringify(data);
100 |
101 | if (callback) {
102 | callback.call(this, [updateData]);
103 | }
104 | }
105 | }
106 |
107 | /**
108 | * Will remove an item from the Store based on its ID
109 | *
110 | * @param {number} id The ID of the item you want to remove
111 | * @param {function} callback The callback to fire after saving
112 | */
113 | remove(id, callback){
114 | const data = JSON.parse(localStorage[this._dbName]);
115 | const todos = data.todos;
116 | const len = todos.length;
117 |
118 | for (let i = 0; i < todos.length; i++) {
119 | if (todos[i].id == id) {
120 | todos.splice(i, 1);
121 | break;
122 | }
123 | }
124 |
125 | localStorage[this._dbName] = JSON.stringify(data);
126 |
127 | if (callback) {
128 | callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
129 | }
130 | }
131 |
132 | /**
133 | * Will drop all storage and start fresh
134 | *
135 | * @param {function} callback The callback to fire after dropping the data
136 | */
137 | drop(callback){
138 | localStorage[this._dbName] = JSON.stringify({todos: []});
139 |
140 | if (callback) {
141 | callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/todomvc/vanilla-es6/src/template.js:
--------------------------------------------------------------------------------
1 | const htmlEscapes = {
2 | '&': '&',
3 | '<': '<',
4 | '>': '>',
5 | '"': '"',
6 | '\'': ''',
7 | '`': '`'
8 | };
9 |
10 | const reUnescapedHtml = /[&<>"'`]/g;
11 | const reHasUnescapedHtml = new RegExp(reUnescapedHtml.source);
12 |
13 | const escape = str => (str && reHasUnescapedHtml.test(str)) ? str.replace(reUnescapedHtml, escapeHtmlChar) : str;
14 | const escapeHtmlChar = chr => htmlEscapes[chr];
15 |
16 | export default class Template {
17 | constructor() {
18 | this.defaultTemplate = `
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | `;
27 | }
28 |
29 | /**
30 | * Creates an
HTML string and returns it for placement in your app.
31 | *
32 | * NOTE: In real life you should be using a templating engine such as Mustache
33 | * or Handlebars, however, this is a vanilla JS example.
34 | *
35 | * @param {object} data The object containing keys you want to find in the
36 | * template to replace.
37 | * @returns {string} HTML String of an