PageRenderTime 69ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/public/javascripts/models.js

https://github.com/jedp/pondr
JavaScript | 515 lines | 358 code | 114 blank | 43 comment | 24 complexity | d3ea7ccb1bcd63cfe93a973560532d64 MD5 | raw file
  1. // for template rendering in the views below
  2. _.templateSettings = {
  3. interpolate : /\{\{(.+?)\}\}/g
  4. };
  5. function listsAreEqual(a, b) {
  6. if (a.length !== b.length) return false;
  7. for (var i=0; i<a.length; i++) {
  8. if (a[i] !== b[i]) return false;
  9. }
  10. return true;
  11. }
  12. var Suggestion = Backbone.Model.extend({});
  13. var SuggestionView = Backbone.View.extend({
  14. model: Suggestion,
  15. tagName: "li",
  16. className: "suggestion",
  17. template: _.template( $('#suggestion-template').html() ),
  18. initialize: function() {
  19. _.bindAll(this, 'render');
  20. },
  21. render: function() {
  22. $(this.el).html(this.template(this.model.toJSON()));
  23. return this;
  24. }
  25. });
  26. var Response = Backbone.Model.extend({
  27. defaults: {
  28. score: 0,
  29. parentCommentId: null
  30. },
  31. // When we save a comment, we let the server take care
  32. // of folding it into the Wish model and saving the whole
  33. // thing.
  34. url: function() {
  35. if (this.isNew) {
  36. return '/response';
  37. } else {
  38. return '/response/' + this.get('_id');
  39. }
  40. },
  41. upVote: function(username) {
  42. now.upVoteResponse(this.get('_id'), username);
  43. }
  44. });
  45. var ResponseView = Backbone.View.extend({
  46. model: Response,
  47. tagName: "div",
  48. template: _.template( $('#response-template').html() ),
  49. initialize: function() {
  50. _.bindAll(this, 'render');
  51. this.model.bind('change', this.render);
  52. this.model.view = this;
  53. },
  54. render: function() {
  55. $(this.el).html(this.template(this.model.toJSON()));
  56. return this;
  57. }
  58. });
  59. var ResponseCollection = Backbone.Collection.extend({
  60. model: Response
  61. })
  62. // ----------------------------------------------------------------------
  63. // Comments.
  64. //
  65. // Comments are children of a Wish.
  66. // Each comment can have a collection of Responses to it
  67. var Comment = Backbone.Model.extend({
  68. defaults: {
  69. score: 0,
  70. parentWishId: null
  71. },
  72. // When we save a comment, we let the server take care
  73. // of folding it into the Wish model and saving the whole
  74. // thing.
  75. url: function() {
  76. if (this.isNew) {
  77. return '/comment';
  78. } else {
  79. return '/comment/' + this.get('_id');
  80. }
  81. },
  82. initialize: function() {
  83. if (! this.get('created') ) {
  84. this.set({created: new Date});
  85. }
  86. this.responses = new ResponseCollection;
  87. this.responses.refresh(this.get('responses'));
  88. },
  89. upVote: function(username) {
  90. now.upVoteComment(this.get('_id'), username);
  91. }
  92. });
  93. var CommentView = Backbone.View.extend({
  94. model: Comment,
  95. tagName: "div",
  96. template: _.template( $('#comment-template').html() ),
  97. events: {
  98. 'click .new-response': 'new_response'
  99. },
  100. initialize: function() {
  101. _.bindAll(this, 'render');
  102. this.model.bind('change', this.render);
  103. this.model.view = this;
  104. },
  105. new_response: function(event) {
  106. event.stopPropagation();
  107. event.preventDefault();
  108. var textarea = $(event.currentTarget).closest('.addResponse').find('textarea');
  109. var text = textarea.val();
  110. textarea.val("");
  111. console.log("got text: " + text);
  112. text = text.trim();
  113. if (! text) {
  114. return;
  115. }
  116. var response = new Response({
  117. parentWishId: this.model.get('parentWishId'),
  118. parentCommentId: this.model.get('_id'),
  119. text: text,
  120. whose: this.model.get('whose')});
  121. this.model.responses.add(response);
  122. console.log("adding: " + response);
  123. response.save();
  124. this.render();
  125. },
  126. render: function() {
  127. var self = this;
  128. $(this.el).html(this.template(this.model.toJSON()));
  129. this.model.responses.each( function(response) {
  130. var responseView = new ResponseView({model: response});
  131. self.$('.responses').append(responseView.render().el);
  132. });
  133. return this;
  134. }
  135. });
  136. var CommentCollection = Backbone.Collection.extend({
  137. model: Comment
  138. });
  139. // ----------------------------------------------------------------------
  140. var Wish = Backbone.Model.extend({
  141. url: function() {
  142. if (this.id && this.id.match(/random.json.*/)) {
  143. return '/wish/' + this.id;
  144. } else if (this.isNew) {
  145. return '/wish';
  146. } else {
  147. return '/wish/' + this.get('_id');
  148. }
  149. },
  150. initialize: function() {
  151. if (! this.get('created') ) {
  152. this.set({created: new Date});
  153. }
  154. this.comments = new CommentCollection;
  155. this.comments.refresh(this.get('comments'));
  156. },
  157. voteFor: function(username) {
  158. now.voteForWish(this.get('_id'), username);
  159. }
  160. });
  161. var WishView = Backbone.View.extend({
  162. model: Wish,
  163. tagName: "div",
  164. template: _.template( $('#wish-template').html() ),
  165. events: {
  166. 'click .new-comment': 'new_comment',
  167. 'click .more': 'more',
  168. 'click .flag': 'flag'
  169. },
  170. initialize: function() {
  171. _.bindAll(this, 'render');
  172. this.model.bind('change', this.render);
  173. this.model.view = this;
  174. this.viewer = null;
  175. },
  176. setViewer: function(username) {
  177. this.viewer = username;
  178. },
  179. more: function(event) {
  180. // When somebody clicks the 'more' button on an item,
  181. // hide all the other items. (The view for the clicked
  182. // item itself deals with toggling itself open/closed.)
  183. var button = $(event.currentTarget);
  184. var wishElem = button.closest('.wish');
  185. if (button.text() === 'show discussion') {
  186. button.text("hide discussion");
  187. } else {
  188. button.text("show discussion");
  189. }
  190. this.$('.comments-container').toggle();
  191. // bug - this can move the location of the button in the flow
  192. // with no mouseout event, so the button will remain highlighted
  193. $('#application .wish').not(wishElem).toggle();
  194. },
  195. flag: function(event) {
  196. // flag as inappropriate
  197. console.log('flagged by ' + this.viewer);
  198. },
  199. new_comment: function(event) {
  200. event.stopPropagation();
  201. event.preventDefault();
  202. // refactor w/ new_response
  203. var textarea = $(event.currentTarget).closest('.addComment').find('textarea');
  204. var text = textarea.val();
  205. textarea.val("");
  206. text = text.trim();
  207. if (! text) {
  208. return;
  209. }
  210. var comment = new Comment({
  211. parentWishId: this.model.get('_id'),
  212. text: text,
  213. whose: this.model.get('whose')});
  214. this.model.comments.add(comment);
  215. comment.save();
  216. // add a view
  217. commentView = new CommentView({model: comment});
  218. self.$('.comments').append(commentView.render().el);
  219. },
  220. edit: function() {
  221. $(this.el).addClass("editing");
  222. // noodly oodly oodly
  223. this.input.focus();
  224. },
  225. render: function() {
  226. var self = this;
  227. $(this.el).html(this.template(this.model.toJSON()));
  228. this.model.comments.each( function(comment) {
  229. comment.set({'parentWishId': self.model.get('_id')});
  230. commentView = new CommentView({model: comment});
  231. self.$('.comments').append(commentView.render().el);
  232. });
  233. return this;
  234. }
  235. });
  236. var WishCollection = Backbone.Collection.extend({ model: Wish });
  237. // ----------------------------------------------------------------------
  238. // the view for the vote page - the main application view
  239. var NewWishApplication = Backbone.Model.extend({});
  240. var NewWishApplicationView = Backbone.View.extend({
  241. model: Wish,
  242. el: $('#wish-application'),
  243. initialize: function() {
  244. this.currentCompletions = [];
  245. this.haveComment = false;
  246. },
  247. events: {
  248. 'click .submit': 'commit',
  249. 'keydown .newWish': 'keyDown',
  250. 'click .newWish': 'cancelSearch'
  251. },
  252. cancelSearch: function(event) {
  253. this.$('li.suggestion').remove();
  254. },
  255. maybeAddComment: function(text) {
  256. // As the user writes a new wish, once sentence-final punctuation is
  257. // encountered, add a Comment and move keyboard focus to the comment.
  258. if (this.haveComment === true) {
  259. return;
  260. }
  261. // has the user typed a sentence?
  262. var sentences = text.match(/([^\.]+\.)\s+(.*)/);
  263. if (sentences) {
  264. var wish = sentences[1].trim();
  265. var comment = sentences[2].trim();
  266. this.$('.newWish-why').show();
  267. this.$('.comments-container').show();
  268. $( this.$('.comments-container')[0]).focus();
  269. this.$('.textarea-container textarea').val(wish);
  270. this.$('.comments-container textarea').val(comment);
  271. // update save method
  272. this.haveComment = true;
  273. }
  274. },
  275. autocompleteSearch: function(text) {
  276. // bug - doesn't catch select-all + delete
  277. // doesn't catch ctrl-a ctrl-k
  278. if (text.trim() === "") {
  279. return this.$('li.suggestion').remove();
  280. }
  281. var self = this;
  282. // replace existing suggestion list with
  283. // results from completer search
  284. now.search(text, 10, function(err, results) {
  285. if (!listsAreEqual(results, self.currentCompletions)) {
  286. self.$('li.suggestion').remove();
  287. _.each(results, function(result) {
  288. // results are lists of [docid:text, ...]
  289. // so split the id and the text apart
  290. var sep = result.match(":").index;
  291. var id = result.slice(0, sep);
  292. var text = result.slice(sep+1);
  293. var suggestion = new Suggestion({_id: id, text: text});
  294. var view = new SuggestionView({model:suggestion});
  295. self.$('.suggestions').append(view.render().el);
  296. });
  297. self.currentCompletions = results;
  298. }
  299. });
  300. },
  301. keyDown: function(event) {
  302. // grab the existing text, and the key just pressed
  303. var text = $('.newWish textarea').val();
  304. text += String.fromCharCode(event.which);
  305. this.autocompleteSearch(text);
  306. this.maybeAddComment(text);
  307. },
  308. commit: function(event) {
  309. //var wish = new Wish();
  310. var comment = new Comment;
  311. var commentCollection = new CommentCollection;
  312. comment.set({
  313. text: this.$('.newWishComment textarea').val(),
  314. whose: this.model.get('whose')});
  315. commentCollection.add(comment);
  316. this.model.set({
  317. text: this.$('.newWish textarea').val(),
  318. comments: commentCollection});
  319. this.model.save();
  320. this.el.fadeOut(200);
  321. // having made a wish, go compare with others
  322. location.replace('/vote/'+this.model.get('_id'))
  323. }
  324. });
  325. var ListApplication = Backbone.Model.extend({});
  326. var ListApplicationView = Backbone.View.extend({
  327. model: ListApplication,
  328. el: $("#list-application"),
  329. initialize: function() {
  330. _.bindAll(this, 'addOne', 'render');
  331. },
  332. addOne: function(wish) {
  333. var view = new WishView({model: wish});
  334. $(this.el).append(view.render().el);
  335. }
  336. });
  337. // ----------------------------------------------------------------------
  338. // voting for one wish over another
  339. var VoteApplication = Backbone.Model.extend({});
  340. var VoteApplicationView = Backbone.View.extend({
  341. model: VoteApplication,
  342. el: $("#vote-application"),
  343. events: {
  344. 'click .vote': 'vote',
  345. },
  346. initialize: function() {
  347. this.views = [];
  348. _.bindAll(this, 'addOne', 'render');
  349. // we would fetch() here, except that the stuff's already in the dom
  350. // via the server template.
  351. },
  352. vote: function(event) {
  353. var self = this;
  354. var notIds = _.map(self.views, function(view) { return view.model.get('_id')} ).join(',');
  355. var toRemove = [];
  356. _.each(this.views, function(view) {
  357. if (! view.el.contains(event.currentTarget)) {
  358. toRemove.unshift(view);
  359. } else {
  360. view.model.voteFor(self.model.get('username'));
  361. }
  362. });
  363. // remove views that weren't voted for
  364. _.each(toRemove, function(r) { self.removeView(r)});
  365. // replace views that weren't voted for
  366. _.each(toRemove, function() {
  367. var wish = new Wish({id: 'random.json/not/'+notIds});
  368. wish.fetch({
  369. error: function(resp) {
  370. console.error(resp);
  371. },
  372. success: function(model, resp) {
  373. self.addOne(wish);
  374. }
  375. });
  376. });
  377. },
  378. removeView: function(view) {
  379. this.views.splice(this.views.indexOf(view), 1);
  380. view.remove();
  381. },
  382. addOne: function(wish) {
  383. var view = new WishView({model: wish});
  384. view.setViewer(this.model.get('username'));
  385. var newEl = view.render().el;
  386. $(this.el).append(newEl);
  387. this.views.unshift(view);
  388. },
  389. });
  390. var WhatsNewApplication = Backbone.Model.extend({});
  391. var WhatsNewApplicationView = Backbone.View.extend({
  392. model: WhatsNewApplication,
  393. el: $("#whats-new-application"),
  394. initialize: function() {
  395. _.bindAll(this, 'addOne', 'render');
  396. },
  397. addOne: function(wish) {
  398. var view = new WishView({model: wish});
  399. $(this.el).append(view.render().el);
  400. }
  401. });