/js/todos.js

https://github.com/larrymyers/backbone-koans · JavaScript · 209 lines · 151 code · 57 blank · 1 comment · 12 complexity · 3b9097d4b4b52928287ef2408b0dc124 MD5 · raw file

  1. /* global $, _, Backbone */
  2. (function() {
  3. var TEMPLATE_URL = '';
  4. var NAUGHTY_WORDS = /crap|poop|hell|frogs/gi;
  5. function sanitize(str) {
  6. return str.replace(NAUGHTY_WORDS, 'double rainbows');
  7. }
  8. window.Todo = Backbone.Model.extend({
  9. defaults: function() {
  10. return {
  11. text: '',
  12. done: false,
  13. order: 0
  14. };
  15. },
  16. initialize: function() {
  17. this.set({text: sanitize(this.get('text'))}, {silent: true});
  18. },
  19. validate: function(attrs) {
  20. if (attrs.hasOwnProperty('done') && !_.isBoolean(attrs.done)) {
  21. return 'Todo.done must be a boolean value.';
  22. }
  23. },
  24. toggle: function() {
  25. this.save({done: !this.get("done")});
  26. }
  27. });
  28. window.TodoList = Backbone.Collection.extend({
  29. model: Todo,
  30. url: '/todos/',
  31. done: function() {
  32. return this.filter(function(todo) { return todo.get('done'); });
  33. },
  34. remaining: function() {
  35. return this.without.apply(this, this.done());
  36. },
  37. nextOrder: function() {
  38. if (!this.length) {
  39. return 1;
  40. }
  41. return this.last().get('order') + 1;
  42. },
  43. comparator: function(todo) {
  44. return todo.get('order');
  45. }
  46. });
  47. window.TodoView = Backbone.View.extend({
  48. tagName: "li",
  49. events: {
  50. "click .check" : "toggleDone",
  51. "dblclick div.todo-text" : "edit",
  52. "click span.todo-destroy" : "clear",
  53. "keypress .todo-input" : "updateOnEnter"
  54. },
  55. initialize: function() {
  56. this.model.on('change', this.render, this);
  57. this.model.on('destroy', this.remove, this);
  58. },
  59. render: function() {
  60. var self = this;
  61. $(self.el).empty().template(TEMPLATE_URL + '/templates/item.html', self.model.toJSON(), function() {
  62. self.setText();
  63. });
  64. return this;
  65. },
  66. setText: function() {
  67. var text = this.model.get('text');
  68. this.$('.todo-text').text(text);
  69. this.input = this.$('.todo-input');
  70. this.input.on('blur', _.bind(this.close, this)).val(text);
  71. },
  72. toggleDone: function() {
  73. this.model.toggle();
  74. },
  75. edit: function() {
  76. $(this.el).addClass("editing");
  77. this.input.focus();
  78. },
  79. close: function() {
  80. this.model.save({text: this.input.val()});
  81. $(this.el).removeClass("editing");
  82. },
  83. updateOnEnter: function(e) {
  84. if (e.keyCode === 13) { this.close(); }
  85. },
  86. remove: function() {
  87. $(this.el).remove();
  88. },
  89. clear: function() {
  90. this.model.destroy();
  91. }
  92. });
  93. window.TodoApp = Backbone.View.extend({
  94. todos: new TodoList(),
  95. events: {
  96. "keypress #new-todo": "createOnEnter",
  97. "keyup #new-todo": "showTooltip",
  98. "click .todo-clear a": "clearCompleted"
  99. },
  100. initialize: function(options) {
  101. var self = this,
  102. parentElt = options.appendTo || $('body');
  103. TEMPLATE_URL = options.templateUrl || TEMPLATE_URL;
  104. parentElt.template(TEMPLATE_URL + '/templates/app.html', {}, function() {
  105. self.setElement($('#todoapp'));
  106. self.input = self.$("#new-todo");
  107. self.todos.on('add', self.addOne, self);
  108. self.todos.on('reset', self.addAll, self);
  109. self.todos.on('all', self.render, self);
  110. self.todos.fetch();
  111. });
  112. },
  113. render: function() {
  114. var self = this,
  115. data = {
  116. total: self.todos.length,
  117. done: self.todos.done().length,
  118. remaining: self.todos.remaining().length
  119. };
  120. $('#todo-stats').empty().template(TEMPLATE_URL + '/templates/stats.html', data);
  121. return this;
  122. },
  123. addOne: function(todo) {
  124. var view = new TodoView({model: todo});
  125. this.$("#todo-list").append(view.render().el);
  126. },
  127. addAll: function() {
  128. this.todos.each(this.addOne);
  129. },
  130. createOnEnter: function(e) {
  131. var text = this.input.val();
  132. if (!text || e.keyCode !== 13) {
  133. return;
  134. }
  135. this.todos.create({text: text, done: false, order: this.todos.nextOrder()});
  136. this.input.val('');
  137. },
  138. clearCompleted: function() {
  139. _.each(this.todos.done(), function(todo) { todo.destroy(); });
  140. return false;
  141. },
  142. showTooltip: function(e) {
  143. var tooltip = this.$(".ui-tooltip-top");
  144. var val = this.input.val();
  145. tooltip.fadeOut();
  146. if (this.tooltipTimeout) { clearTimeout(this.tooltipTimeout); }
  147. if (val === '' || val == this.input.attr('placeholder')) { return; }
  148. var show = function() { tooltip.show().fadeIn(); };
  149. this.tooltipTimeout = _.delay(show, 1000);
  150. }
  151. });
  152. }());