Add Speedometer 2.0 to browserbench.org for final testing
[WebKit-https.git] / Websites / browserbench.org / Speedometer2.0 / resources / todomvc / architecture-examples / react / js / app.jsx
1 /*jshint quotmark:false */
2 /*jshint white:false */
3 /*jshint trailing:false */
4 /*jshint newcap:false */
5 /*global React, Router*/
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
15   app.Utils = {
16     uuid: function () {
17       /*jshint bitwise:false */
18       var i, random;
19       var uuid = '';
20
21       for (i = 0; i < 32; i++) {
22         random = Math.random() * 16 | 0;
23         if (i === 8 || i === 12 || i === 16 || i === 20) {
24           uuid += '-';
25         }
26         uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
27           .toString(16);
28       }
29
30       return uuid;
31     },
32
33     pluralize: function (count, word) {
34       return count === 1 ? word : word + 's';
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   var Utils = app.Utils;
52   // Generic "model" object. You can use whatever
53   // framework you want. For this application it
54   // may not even be worth separating this logic
55   // out, but we do this to demonstrate one way to
56   // separate out parts of your application.
57   app.TodoModel = function (key) {
58     this.key = key;
59     this.todos = [];
60     this.onChanges = [];
61   };
62
63   app.TodoModel.prototype.subscribe = function (onChange) {
64     this.onChanges.push(onChange);
65   };
66
67   app.TodoModel.prototype.inform = function () {
68     this.onChanges.forEach(function (cb) { cb(); });
69   };
70
71   app.TodoModel.prototype.addTodo = function (title) {
72     this.todos = this.todos.concat({
73       id: Utils.uuid(),
74       title: title,
75       completed: false
76     });
77
78     this.inform();
79   };
80
81   app.TodoModel.prototype.toggleAll = function (checked) {
82     // Note: it's usually better to use immutable data structures since they're
83     // easier to reason about and React works very well with them. That's why
84     // we use map() and filter() everywhere instead of mutating the array or
85     // todo items themselves.
86     this.todos = this.todos.map(function (todo) {
87       return Utils.extend({}, todo, {completed: checked});
88     });
89
90     this.inform();
91   };
92
93   app.TodoModel.prototype.toggle = function (todoToToggle) {
94     this.todos = this.todos.map(function (todo) {
95       return todo !== todoToToggle ?
96         todo :
97         Utils.extend({}, todo, {completed: !todo.completed});
98     });
99
100     this.inform();
101   };
102
103   app.TodoModel.prototype.destroy = function (todo) {
104     this.todos = this.todos.filter(function (candidate) {
105       return candidate !== todo;
106     });
107
108     this.inform();
109   };
110
111   app.TodoModel.prototype.save = function (todoToSave, text) {
112     this.todos = this.todos.map(function (todo) {
113       return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text});
114     });
115
116     this.inform();
117   };
118
119   app.TodoModel.prototype.clearCompleted = function () {
120     this.todos = this.todos.filter(function (todo) {
121       return !todo.completed;
122     });
123
124     this.inform();
125   };
126
127
128   var TodoFooter = React.createClass({
129     render: function () {
130       var activeTodoWord = app.Utils.pluralize(this.props.count, 'item');
131       var clearButton = null;
132
133       if (this.props.completedCount > 0) {
134         clearButton = (
135           <button
136             className="clear-completed"
137             onClick={this.props.onClearCompleted}>
138             Clear completed
139           </button>
140         );
141       }
142
143       var nowShowing = this.props.nowShowing;
144       return (
145         <footer className="footer">
146           <span className="todo-count">
147             <strong>{this.props.count}</strong> {activeTodoWord} left
148           </span>
149           <ul className="filters">
150             <li>
151               <a
152                 href="#/"
153                 className={classNames({selected: nowShowing === app.ALL_TODOS})}>
154                   All
155               </a>
156             </li>
157             {' '}
158             <li>
159               <a
160                 href="#/active"
161                 className={classNames({selected: nowShowing === app.ACTIVE_TODOS})}>
162                   Active
163               </a>
164             </li>
165             {' '}
166             <li>
167               <a
168                 href="#/completed"
169                 className={classNames({selected: nowShowing === app.COMPLETED_TODOS})}>
170                   Completed
171               </a>
172             </li>
173           </ul>
174           {clearButton}
175         </footer>
176       );
177     }
178   });
179
180   var ESCAPE_KEY = 27;
181   var ENTER_KEY = 13;
182
183   var TodoItem = React.createClass({
184     handleSubmit: function (event) {
185       var val = this.state.editText.trim();
186       if (val) {
187         this.props.onSave(val);
188         this.setState({editText: val});
189       } else {
190         this.props.onDestroy();
191       }
192     },
193
194     handleEdit: function () {
195       this.props.onEdit();
196       this.setState({editText: this.props.todo.title});
197     },
198
199     handleKeyDown: function (event) {
200       if (event.which === ESCAPE_KEY) {
201         this.setState({editText: this.props.todo.title});
202         this.props.onCancel(event);
203       } else if (event.which === ENTER_KEY) {
204         this.handleSubmit(event);
205       }
206     },
207
208     handleChange: function (event) {
209       if (this.props.editing) {
210         this.setState({editText: event.target.value});
211       }
212     },
213
214     getInitialState: function () {
215       return {editText: this.props.todo.title};
216     },
217
218     /**
219      * This is a completely optional performance enhancement that you can
220      * implement on any React component. If you were to delete this method
221      * the app would still work correctly (and still be very performant!), we
222      * just use it as an example of how little code it takes to get an order
223      * of magnitude performance improvement.
224      */
225     shouldComponentUpdate: function (nextProps, nextState) {
226       return (
227         nextProps.todo !== this.props.todo ||
228         nextProps.editing !== this.props.editing ||
229         nextState.editText !== this.state.editText
230       );
231     },
232
233     /**
234      * Safely manipulate the DOM after updating the state when invoking
235      * `this.props.onEdit()` in the `handleEdit` method above.
236      * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate
237      * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate
238      */
239     componentDidUpdate: function (prevProps) {
240       if (!prevProps.editing && this.props.editing) {
241         var node = React.findDOMNode(this.refs.editField);
242         node.focus();
243         node.setSelectionRange(node.value.length, node.value.length);
244       }
245     },
246
247     render: function () {
248       return (
249         <li className={classNames({
250           completed: this.props.todo.completed,
251           editing: this.props.editing
252         })}>
253           <div className="view">
254             <input
255               className="toggle"
256               type="checkbox"
257               checked={this.props.todo.completed}
258               onChange={this.props.onToggle}
259             />
260             <label onDoubleClick={this.handleEdit}>
261               {this.props.todo.title}
262             </label>
263             <button className="destroy" onClick={this.props.onDestroy} />
264           </div>
265           <input
266             ref="editField"
267             className="edit"
268             value={this.state.editText}
269             onBlur={this.handleSubmit}
270             onChange={this.handleChange}
271             onKeyDown={this.handleKeyDown}
272           />
273         </li>
274       );
275     }
276   });
277
278         var ENTER_KEY = 13;
279
280         var TodoApp = React.createClass({
281                 getInitialState: function () {
282                         return {
283                                 nowShowing: app.ALL_TODOS,
284                                 editing: null,
285                                 newTodo: ''
286                         };
287                 },
288
289                 componentDidMount: function () {
290                         var setState = this.setState;
291                         var router = Router({
292                                 '/': setState.bind(this, {nowShowing: app.ALL_TODOS}),
293                                 '/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}),
294                                 '/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS})
295                         });
296                         router.init('/');
297                 },
298
299                 handleChange: function (event) {
300                         this.setState({newTodo: event.target.value});
301                 },
302
303                 handleNewTodoKeyDown: function (event) {
304                         if (event.keyCode !== ENTER_KEY) {
305                                 return;
306                         }
307
308                         event.preventDefault();
309
310                         var val = this.state.newTodo.trim();
311
312                         if (val) {
313                                 this.props.model.addTodo(val);
314                                 this.setState({newTodo: ''});
315                         }
316                 },
317
318                 toggleAll: function (event) {
319                         var checked = event.target.checked;
320                         this.props.model.toggleAll(checked);
321                 },
322
323                 toggle: function (todoToToggle) {
324                         this.props.model.toggle(todoToToggle);
325                 },
326
327                 destroy: function (todo) {
328                         this.props.model.destroy(todo);
329                 },
330
331                 edit: function (todo) {
332                         this.setState({editing: todo.id});
333                 },
334
335                 save: function (todoToSave, text) {
336                         this.props.model.save(todoToSave, text);
337                         this.setState({editing: null});
338                 },
339
340                 cancel: function () {
341                         this.setState({editing: null});
342                 },
343
344                 clearCompleted: function () {
345                         this.props.model.clearCompleted();
346                 },
347
348                 render: function () {
349                         var footer;
350                         var main;
351                         var todos = this.props.model.todos;
352
353                         var shownTodos = todos.filter(function (todo) {
354                                 switch (this.state.nowShowing) {
355                                 case app.ACTIVE_TODOS:
356                                         return !todo.completed;
357                                 case app.COMPLETED_TODOS:
358                                         return todo.completed;
359                                 default:
360                                         return true;
361                                 }
362                         }, this);
363
364                         var todoItems = shownTodos.map(function (todo) {
365                                 return (
366                                         <TodoItem
367                                                 key={todo.id}
368                                                 todo={todo}
369                                                 onToggle={this.toggle.bind(this, todo)}
370                                                 onDestroy={this.destroy.bind(this, todo)}
371                                                 onEdit={this.edit.bind(this, todo)}
372                                                 editing={this.state.editing === todo.id}
373                                                 onSave={this.save.bind(this, todo)}
374                                                 onCancel={this.cancel}
375                                         />
376                                 );
377                         }, this);
378
379                         var activeTodoCount = todos.reduce(function (accum, todo) {
380                                 return todo.completed ? accum : accum + 1;
381                         }, 0);
382
383                         var completedCount = todos.length - activeTodoCount;
384
385                         if (activeTodoCount || completedCount) {
386                                 footer =
387                                         <TodoFooter
388                                                 count={activeTodoCount}
389                                                 completedCount={completedCount}
390                                                 nowShowing={this.state.nowShowing}
391                                                 onClearCompleted={this.clearCompleted}
392                                         />;
393                         }
394
395                         if (todos.length) {
396                                 main = (
397                                         <section className="main">
398                                                 <input
399                                                         className="toggle-all"
400                                                         type="checkbox"
401                                                         onChange={this.toggleAll}
402                                                         checked={activeTodoCount === 0}
403                                                 />
404                                                 <ul className="todo-list">
405                                                         {todoItems}
406                                                 </ul>
407                                         </section>
408                                 );
409                         }
410
411                         return (
412                                 <div>
413                                         <header className="header">
414                                                 <h1>todos</h1>
415                                                 <input
416                                                         className="new-todo"
417                                                         placeholder="What needs to be done?"
418                                                         value={this.state.newTodo}
419                                                         onKeyDown={this.handleNewTodoKeyDown}
420                                                         onChange={this.handleChange}
421                                                         autoFocus={true}
422                                                 />
423                                         </header>
424                                         {main}
425                                         {footer}
426                                 </div>
427                         );
428                 }
429         });
430
431         var model = new app.TodoModel('react-todos');
432
433         function render() {
434                 ReactDOM.render(
435                         <TodoApp model={model}/>,
436                         document.getElementsByClassName('todoapp')[0]
437                 );
438         }
439
440         model.subscribe(render);
441         render();
442 })();