PageRenderTime 24ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/examples/todomvc-backbone/js/app.js

https://gitlab.com/cly/react
JavaScript | 303 lines | 243 code | 34 blank | 26 comment | 11 complexity | 3ef101254eb84e08bb8f5ab0c649cd91 MD5 | raw file
Possible License(s): Apache-2.0
  1. /** @jsx React.DOM */
  2. var Todo = Backbone.Model.extend({
  3. // Default attributes for the todo
  4. // and ensure that each todo created has `title` and `completed` keys.
  5. defaults: {
  6. title: '',
  7. completed: false
  8. },
  9. // Toggle the `completed` state of this todo item.
  10. toggle: function() {
  11. this.save({
  12. completed: !this.get('completed')
  13. });
  14. }
  15. });
  16. var TodoList = Backbone.Collection.extend({
  17. // Reference to this collection's model.
  18. model: Todo,
  19. // Save all of the todo items under the `"todos"` namespace.
  20. localStorage: new Store('todos-react-backbone'),
  21. // Filter down the list of all todo items that are finished.
  22. completed: function() {
  23. return this.filter(function( todo ) {
  24. return todo.get('completed');
  25. });
  26. },
  27. remaining: function() {
  28. return this.without.apply(this, this.completed());
  29. },
  30. // We keep the Todos in sequential order, despite being saved by unordered
  31. // GUID in the database. This generates the next order number for new items.
  32. nextOrder: function () {
  33. if (!this.length) {
  34. return 1;
  35. }
  36. return this.last().get('order') + 1;
  37. },
  38. // Todos are sorted by their original insertion order.
  39. comparator: function (todo) {
  40. return todo.get('order');
  41. }
  42. });
  43. var Utils = {
  44. pluralize: function( count, word ) {
  45. return count === 1 ? word : word + 's';
  46. },
  47. stringifyObjKeys: function(obj) {
  48. var s = '';
  49. for (var key in obj) {
  50. if (!obj.hasOwnProperty(key)) {
  51. continue;
  52. }
  53. if (obj[key]) {
  54. s += key + ' ';
  55. }
  56. }
  57. return s;
  58. }
  59. };
  60. // Begin React stuff
  61. var TodoItem = React.createClass({
  62. handleSubmit: function(event) {
  63. var val = this.refs.editField.getDOMNode().value.trim();
  64. if (val) {
  65. this.props.onSave(val);
  66. } else {
  67. this.props.onDestroy();
  68. }
  69. return false;
  70. },
  71. onEdit: function() {
  72. this.props.onEdit();
  73. this.refs.editField.getDOMNode().focus();
  74. },
  75. render: function() {
  76. var classes = Utils.stringifyObjKeys({
  77. completed: this.props.todo.get('completed'), editing: this.props.editing
  78. });
  79. return (
  80. <li className={classes}>
  81. <div className="view">
  82. <input
  83. className="toggle"
  84. type="checkbox"
  85. checked={this.props.todo.get('completed')}
  86. onChange={this.props.onToggle}
  87. key={this.props.key}
  88. />
  89. <label onDoubleClick={this.onEdit}>
  90. {this.props.todo.get('title')}
  91. </label>
  92. <button className="destroy" onClick={this.props.onDestroy} />
  93. </div>
  94. <form onSubmit={this.handleSubmit}>
  95. <input
  96. ref="editField"
  97. className="edit"
  98. defaultValue={this.props.todo.get('title')}
  99. onBlur={this.handleSubmit}
  100. autoFocus="autofocus"
  101. />
  102. </form>
  103. </li>
  104. );
  105. }
  106. });
  107. var TodoFooter = React.createClass({
  108. render: function() {
  109. var activeTodoWord = Utils.pluralize(this.props.count, 'todo');
  110. var clearButton = null;
  111. if (this.props.completedCount > 0) {
  112. clearButton = (
  113. <button id="clear-completed" onClick={this.props.onClearCompleted}>
  114. Clear completed ({this.props.completedCount})
  115. </button>
  116. );
  117. }
  118. return (
  119. <footer id="footer">
  120. <span id="todo-count">
  121. <strong>{this.props.count}</strong>{' '}
  122. {activeTodoWord}{' '}left
  123. </span>
  124. {clearButton}
  125. </footer>
  126. );
  127. }
  128. });
  129. // An example generic Mixin that you can add to any component that should react
  130. // to changes in a Backbone component. The use cases we've identified thus far
  131. // are for Collections -- since they trigger a change event whenever any of
  132. // their constituent items are changed there's no need to reconcile for regular
  133. // models. One caveat: this relies on getBackboneModels() to always return the
  134. // same model instances throughout the lifecycle of the component. If you're
  135. // using this mixin correctly (it should be near the top of your component
  136. // hierarchy) this should not be an issue.
  137. var BackboneMixin = {
  138. componentDidMount: function() {
  139. // Whenever there may be a change in the Backbone data, trigger a reconcile.
  140. this.getBackboneModels().forEach(function(model) {
  141. model.on('add change remove', this.forceUpdate.bind(this, null), this);
  142. }, this);
  143. },
  144. componentWillUnmount: function() {
  145. // Ensure that we clean up any dangling references when the component is
  146. // destroyed.
  147. this.getBackboneModels().forEach(function(model) {
  148. model.off(null, null, this);
  149. }, this);
  150. }
  151. };
  152. var TodoApp = React.createClass({
  153. mixins: [BackboneMixin],
  154. getInitialState: function() {
  155. return {editing: null};
  156. },
  157. componentDidMount: function() {
  158. // Additional functionality for todomvc: fetch() the collection on init
  159. this.props.todos.fetch();
  160. this.refs.newField.getDOMNode().focus();
  161. },
  162. componentDidUpdate: function() {
  163. // If saving were expensive we'd listen for mutation events on Backbone and
  164. // do this manually. however, since saving isn't expensive this is an
  165. // elegant way to keep it reactively up-to-date.
  166. this.props.todos.forEach(function(todo) {
  167. todo.save();
  168. });
  169. },
  170. getBackboneModels: function() {
  171. return [this.props.todos];
  172. },
  173. handleSubmit: function(event) {
  174. event.preventDefault();
  175. var val = this.refs.newField.getDOMNode().value.trim();
  176. if (val) {
  177. this.props.todos.create({
  178. title: val,
  179. completed: false,
  180. order: this.props.todos.nextOrder()
  181. });
  182. this.refs.newField.getDOMNode().value = '';
  183. }
  184. },
  185. toggleAll: function(event) {
  186. var checked = event.nativeEvent.target.checked;
  187. this.props.todos.forEach(function(todo) {
  188. todo.set('completed', checked);
  189. });
  190. },
  191. edit: function(todo) {
  192. this.setState({editing: todo.get('id')});
  193. },
  194. save: function(todo, text) {
  195. todo.set('title', text);
  196. this.setState({editing: null});
  197. },
  198. clearCompleted: function() {
  199. this.props.todos.completed().forEach(function(todo) {
  200. todo.destroy();
  201. });
  202. },
  203. render: function() {
  204. var footer = null;
  205. var main = null;
  206. var todoItems = this.props.todos.map(function(todo) {
  207. return (
  208. <TodoItem
  209. key={todo.cid}
  210. todo={todo}
  211. onToggle={todo.toggle.bind(todo)}
  212. onDestroy={todo.destroy.bind(todo)}
  213. onEdit={this.edit.bind(this, todo)}
  214. editing={this.state.editing === todo.get('id')}
  215. onSave={this.save.bind(this, todo)}
  216. />
  217. );
  218. }, this);
  219. var activeTodoCount = this.props.todos.remaining().length;
  220. var completedCount = todoItems.length - activeTodoCount;
  221. if (activeTodoCount || completedCount) {
  222. footer =
  223. <TodoFooter
  224. count={activeTodoCount}
  225. completedCount={completedCount}
  226. onClearCompleted={this.clearCompleted}
  227. />;
  228. }
  229. if (todoItems.length) {
  230. main = (
  231. <section id="main">
  232. <input id="toggle-all" type="checkbox" onChange={this.toggleAll} />
  233. <ul id="todo-list">
  234. {todoItems}
  235. </ul>
  236. </section>
  237. );
  238. }
  239. return (
  240. <div>
  241. <section id="todoapp">
  242. <header id="header">
  243. <h1>todos</h1>
  244. <form onSubmit={this.handleSubmit}>
  245. <input
  246. ref="newField"
  247. id="new-todo"
  248. placeholder="What needs to be done?"
  249. />
  250. </form>
  251. </header>
  252. {main}
  253. {footer}
  254. </section>
  255. <footer id="info">
  256. <p>Double-click to edit a todo</p>
  257. <p>
  258. Created by{' '}
  259. <a href="http://github.com/petehunt/">petehunt</a>
  260. </p>
  261. <p>Part of{' '}<a href="http://todomvc.com">TodoMVC</a></p>
  262. </footer>
  263. </div>
  264. );
  265. }
  266. });
  267. React.renderComponent(
  268. <TodoApp todos={new TodoList()} />, document.getElementById('container')
  269. );