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