PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/public/javascripts/twalks.js

https://github.com/everzet/twalks
JavaScript | 582 lines | 484 code | 82 blank | 16 comment | 27 complexity | 6293f30234132e1b23748f533e5736b6 MD5 | raw file
  1. (function($) {
  2. // Replace ERB-style delimiters for Underscore templates
  3. _.templateSettings = {
  4. interpolate: /\{\{(.+?)\}\}/g
  5. , evaluate: /\{\%(.+?)\%\}/g
  6. };
  7. // Enable close button on alert messages
  8. $('.alert-message a.close').live('click', function(ev) {
  9. $(this).parent().slideUp('fast', function(){ $(this).remove(); });
  10. ev.preventDefault();
  11. });
  12. var alertMessage = function(type, msg) {
  13. if (-1 == ['error', 'info' , 'success', 'warning'].indexOf(type)) {
  14. return;
  15. }
  16. $('<div class="alert-message"><a href="#" class="close">×</a><p></p></div>')
  17. .addClass(type)
  18. .children('p')
  19. .text(msg)
  20. .end()
  21. .prependTo('#bb-content');
  22. }
  23. var clearAlertMessages = function() {
  24. $('#bb-content .alert-message').remove();
  25. }
  26. var fieldError = function(field, error) {
  27. var pos = $(field).position()
  28. , height = $(field).height()
  29. , width = $(field).width()
  30. , msg;
  31. if ('required' == error.type) {
  32. msg = 'The '+error.path+' is required.';
  33. } else if ('regexp' == error.type) {
  34. msg = 'The '+error.path+' is invalid.';
  35. } else {
  36. msg = 'Something is wrong with this field, but we\'re not sure why.';
  37. }
  38. $('<div class="twipsy right field-error"><div class="twipsy-arrow"></div><div class="twipsy-inner"></div>')
  39. .children('.twipsy-inner')
  40. .text(msg)
  41. .end()
  42. .insertAfter(field)
  43. .css('top', pos.top)
  44. .css('left', pos.left + width + 15);
  45. }
  46. var clearFieldErrors = function() {
  47. $('#bb-content .field-error').remove();
  48. }
  49. // see: http://stackoverflow.com/questions/1184624/serialize-form-to-json-with-jquery
  50. $.fn.serializeObject = function() {
  51. var o = {}
  52. , a = this.serializeArray();
  53. $.each(a, function() {
  54. if (o[this.name] !== undefined) {
  55. if (!o[this.name].push) {
  56. o[this.name] = [o[this.name]];
  57. }
  58. o[this.name].push(this.value || '');
  59. } else {
  60. o[this.name] = this.value || '';
  61. }
  62. });
  63. return o;
  64. };
  65. var App = {
  66. Collections: {}
  67. , Models: {}
  68. , Routers: {}
  69. , Views: {}
  70. , init: function() {
  71. this.router = new App.Routers.Events();
  72. Backbone.history.start();
  73. }
  74. };
  75. /**
  76. * :: Models
  77. */
  78. App.Models.Event = Backbone.Model.extend({
  79. url: function() {
  80. return '/events/' + (this.isNew() ? 'new' : this.id) + '.json';
  81. }
  82. });
  83. App.Models.Tweet = Backbone.Model.extend({});
  84. App.Models.Asset = Backbone.Model.extend({});
  85. /**
  86. * :: Collections
  87. */
  88. App.Collections.Events = Backbone.Collection.extend({
  89. model: App.Models.Event
  90. , url: '/events.json'
  91. });
  92. App.Collections.EventsCurrent = App.Collections.Events.extend({
  93. url: '/events/current.json'
  94. });
  95. App.Collections.EventsUpcoming = Backbone.Collection.extend({
  96. url: '/events/upcoming.json'
  97. });
  98. App.Collections.EventsMy = Backbone.Collection.extend({
  99. url: '/events/my.json'
  100. });
  101. App.Collections.EventTweets = Backbone.Collection.extend({
  102. model: App.Models.Tweet
  103. , url: function() {
  104. return '/events/' + this.eventId + '/tweets.json';
  105. }
  106. , initialize: function(models, options) {
  107. this.eventId = options.eventId;
  108. }
  109. });
  110. App.Collections.EventPhotos = Backbone.Collection.extend({
  111. model: App.Models.Asset
  112. , url: function() {
  113. return '/events/' + this.eventId + '/assets/photo.json';
  114. }
  115. , initialize: function(models, options) {
  116. this.eventId = options.eventId;
  117. }
  118. });
  119. App.Collections.EventVideos = Backbone.Collection.extend({
  120. model: App.Models.Asset
  121. , url: function() {
  122. return '/events/' + this.eventId + '/assets/video.json';
  123. }
  124. , initialize: function(models, options) {
  125. this.eventId = options.eventId;
  126. }
  127. });
  128. /**
  129. * :: Views
  130. */
  131. App.Views.Event = Backbone.View.extend({
  132. initialize: function(options) {
  133. this.talksCollection = options.talksCollection;
  134. this.tweetsCollection = options.tweetsCollection;
  135. this.photosCollection = options.photosCollection;
  136. this.videosCollection = options.videosCollection;
  137. this.template = _.template($('#event-show-template').html());
  138. }
  139. , render: function() {
  140. $(this.el).html(this.template(this.model.toJSON()));
  141. var self = this,
  142. $tabsContent = this.$('.event-footer-content');
  143. this.$('.tabs .tweets a').click(function() {
  144. self.$('.tabs li.active').removeClass('active');
  145. $(this).parent().addClass('active');
  146. self.tweetsCollection.fetch({ success: function() {
  147. var view = new App.Views.TweetsList({ collection: self.tweetsCollection });
  148. $tabsContent.empty().append(view.render().el);
  149. }});
  150. }).click();
  151. this.$('.tabs .photos a').click(function() {
  152. self.$('.tabs li.active').removeClass('active');
  153. $(this).parent().addClass('active');
  154. self.photosCollection.fetch({ success: function() {
  155. var view = new App.Views.PhotosList({ collection: self.photosCollection });
  156. $tabsContent.empty().append(view.render().el);
  157. }});
  158. });
  159. this.$('.tabs .videos a').click(function() {
  160. self.$('.tabs li.active').removeClass('active');
  161. $(this).parent().addClass('active');
  162. self.videosCollection.fetch({ success: function() {
  163. var view = new App.Views.VideosList({ collection: self.videosCollection });
  164. $tabsContent.empty().append(view.render().el);
  165. }});
  166. });
  167. return this;
  168. }
  169. });
  170. App.Views.EventsListEvent = App.Views.Event.extend({
  171. tagName: 'article'
  172. , initialize: function() {
  173. this.template = _.template($('#events-list-event-template').html());
  174. }
  175. });
  176. App.Views.EventsList = Backbone.View.extend({
  177. initialize: function() {
  178. _.bindAll(this, 'render');
  179. this.template = _.template($('#events-list-template').html());
  180. this.collection.bind('reset', this.render);
  181. }
  182. , render: function() {
  183. if (this.collection.length > 0) {
  184. var $list;
  185. $(this.el).html(this.template({}));
  186. $list = this.$('.list');
  187. this.collection.each(function(event) {
  188. var view = new App.Views.EventsListEvent({ model: event });
  189. $list.append(view.render().el);
  190. });
  191. } else {
  192. $(this.el).html($('#events-list-empty-template').html());
  193. }
  194. return this;
  195. }
  196. });
  197. App.Views.TweetsList = App.Views.Event.extend({
  198. initialize: function() {
  199. this.template = _.template($('#tweets-list-template').html());
  200. }
  201. , render: function() {
  202. $(this.el).html(this.template({ 'tweets': this.collection.toJSON() }));
  203. return this;
  204. }
  205. });
  206. App.Views.PhotosList = App.Views.Event.extend({
  207. initialize: function() {
  208. this.template = _.template($('#photos-list-template').html());
  209. }
  210. , render: function() {
  211. $(this.el).html(this.template({ 'photos': this.collection.toJSON() }));
  212. $(this.el).find('a.fancybox-group').attr('rel', 'gallery').fancybox();
  213. return this;
  214. }
  215. });
  216. App.Views.VideosList = App.Views.Event.extend({
  217. initialize: function() {
  218. this.template = _.template($('#videos-list-template').html());
  219. }
  220. , render: function() {
  221. $(this.el).html(this.template({ 'videos': this.collection.toJSON() }));
  222. return this;
  223. }
  224. });
  225. // :: Forms
  226. App.Views.EventForm = Backbone.View.extend({
  227. initialize: function() {
  228. this.template = _.template($('#event-form-template').html());
  229. }
  230. , events: {
  231. 'submit form': 'save'
  232. }
  233. , render: function() {
  234. $(this.el).html(this.template({ model : this.model }));
  235. return this;
  236. }
  237. , save: function() {
  238. var self = this
  239. , msg = this.model.isNew() ? 'Successfully created!' : 'Saved!'
  240. , data = this.$('form').serializeObject();
  241. data.hash = '#' + data.hash;
  242. console.log(data);
  243. this.$('form .error').removeClass('error');
  244. clearAlertMessages();
  245. clearFieldErrors();
  246. this.model.save(data, {
  247. success: function(model, err) {
  248. self.model = model;
  249. self.render();
  250. self.delegateEvents();
  251. window.App.router.navigate('events/' + model.get('_id'), true);
  252. }
  253. , error: function(model, err) {
  254. if (403 == err.status) {
  255. alertMessage('warning', 'You must be signed in to submit this form. Please sign in using the link at the top right.');
  256. return;
  257. }
  258. alertMessage('error', 'Oops! There was a problem submitting this form. Please fix the errors below and try again.');
  259. var errors = $.parseJSON(err.responseText)
  260. , self = this;
  261. _.each(errors, function(error, name) {
  262. var field = this.$('form .' + name)
  263. , lastInput = $(field).find(':input:last');
  264. field.addClass('error');
  265. fieldError(lastInput, error);
  266. });
  267. }
  268. });
  269. return false;
  270. }
  271. });
  272. var eventsCollection = new App.Collections.Events();
  273. /**
  274. * :: Routers
  275. */
  276. App.Routers.Events = Backbone.Router.extend({
  277. routes: {
  278. '': 'home'
  279. , 'events/current': 'listCurrent'
  280. , 'events/upcoming': 'listUpcoming'
  281. , 'events/my': 'listMy'
  282. , 'events/new': 'createEvent'
  283. , 'events/:id/edit': 'editEvent'
  284. , 'events': 'listEvents'
  285. , 'events/:id': 'showEvent'
  286. }
  287. , initialize: function() {
  288. this.$container = $('#bb-content');
  289. this.$navigation = $('#navigation');
  290. this.$secondaryNav = $('.secondary-nav', this.$navigation);
  291. this.eventsListView = new App.Views.EventsList({ collection: eventsCollection });
  292. this.$searchInput = $('#search-form input');
  293. var self = this
  294. , timeout;
  295. this.$searchInput.keyup(function() {
  296. clearTimeout(timeout);
  297. timeout = setTimeout(function() {
  298. if ('' !== self.$searchInput.val().replace(/^ +| +$/, '')) {
  299. self.hideAndEmptyContainer(function() {
  300. self.listEvents(true);
  301. });
  302. }
  303. clearTimeout(timeout);
  304. }, 500);
  305. });
  306. }
  307. , home: function() {
  308. if ($('#navigation .user.menu.loggedIn').length) {
  309. this.navigate('events/my', true);
  310. return;
  311. }
  312. $('li.active', this.$navigation).removeClass('active');
  313. var self = this;
  314. this.hideAndEmptyContainer(function() {
  315. document.title = 'Twalks';
  316. self.displayContainer($('#welcome-template').html());
  317. self.$searchInput.val('');
  318. });
  319. }
  320. , createEvent: function() {
  321. $('li.active', this.$navigation).removeClass('active');
  322. $('li.new-event', this.$navigation).addClass('active');
  323. var self = this;
  324. this.hideAndEmptyContainer(function() {
  325. document.title = 'New event :: Twalks';
  326. var eventFormView = new App.Views.EventForm({ model: new App.Models.Event() });
  327. self.displayContainer(eventFormView.render().el);
  328. });
  329. }
  330. , editEvent: function(id) {
  331. this.showProgressBar();
  332. $('li.active', this.$navigation).removeClass('active');
  333. var event = new App.Models.Event({ 'id': id })
  334. , self = this;
  335. event.fetch({
  336. success: function(model, res) {
  337. var eventFormView = new App.Views.EventForm({ model: event });
  338. self.hideAndEmptyContainer(function() {
  339. document.title = 'Edit '+model.get('name')+' :: Twalks';
  340. self.displayContainer(eventFormView.render().el);
  341. });
  342. }
  343. , error: function() {
  344. self.hideProgressBar();
  345. self.navigate('', true);
  346. }
  347. });
  348. }
  349. , listEvents: function(withoutAnimation) {
  350. this.showProgressBar();
  351. $('li.active', this.$navigation).removeClass('active');
  352. $('li.all-events', this.$navigation).addClass('active');
  353. var self = this
  354. , filter = this.$searchInput.val().replace(/^ +| +$/, '');
  355. if ('' !== filter) {
  356. eventsCollection.url = '/events.json?q='+filter;
  357. } else {
  358. eventsCollection.url = '/events.json';
  359. }
  360. eventsCollection.fetch({
  361. success: function() {
  362. if (withoutAnimation) {
  363. self.displayContainer(self.eventsListView.render().el);
  364. } else {
  365. self.hideAndEmptyContainer(function(){
  366. document.title = 'Events :: Twalks';
  367. self.displayContainer(self.eventsListView.render().el);
  368. });
  369. }
  370. }
  371. , error: function(){
  372. alertMessage('warning', 'We encountered an error fetching your events.');
  373. }
  374. });
  375. }
  376. , listMy: function() {
  377. this.showProgressBar();
  378. $('li.active', this.$navigation).removeClass('active');
  379. $('li.my-events', this.$navigation).addClass('active');
  380. var self = this
  381. , collection = new App.Collections.EventsMy
  382. , listView = new App.Views.EventsList({ collection: collection });
  383. collection.fetch({
  384. success: function() {
  385. self.hideAndEmptyContainer(function() {
  386. document.title = 'My events :: Twalks';
  387. self.displayContainer(listView.render().el);
  388. });
  389. }
  390. , error: function(model, err){
  391. if (403 == err.status) {
  392. alertMessage('warning', 'You must be signed in to view your events. Please sign in using the link at the top right.');
  393. return;
  394. }
  395. alertMessage('warning', 'We encountered an error fetching your events.');
  396. }
  397. });
  398. }
  399. , listCurrent: function() {
  400. this.showProgressBar();
  401. $('li.active', this.$navigation).removeClass('active');
  402. $('li.current-events', this.$navigation).addClass('active');
  403. var self = this
  404. , collection = new App.Collections.EventsCurrent
  405. , listView = new App.Views.EventsList({ collection: collection });
  406. collection.fetch({
  407. success: function() {
  408. self.hideAndEmptyContainer(function() {
  409. document.title = 'Ongoing events :: Twalks';
  410. self.displayContainer(listView.render().el);
  411. });
  412. }
  413. , error: function(){
  414. alertMessage('warning', 'We encountered an error fetching ongoing events.');
  415. }
  416. });
  417. }
  418. , listUpcoming: function() {
  419. this.showProgressBar();
  420. $('li.active', this.$navigation).removeClass('active');
  421. $('li.upcoming-events', this.$navigation).addClass('active');
  422. var self = this
  423. , collection = new App.Collections.EventsUpcoming
  424. , listView = new App.Views.EventsList({ collection: collection });
  425. collection.fetch({
  426. success: function() {
  427. self.hideAndEmptyContainer(function() {
  428. document.title = 'Upcomming events :: Twalks';
  429. self.displayContainer(listView.render().el);
  430. });
  431. }
  432. , error: function(){
  433. alertMessage('warning', 'We encountered an error fetching upcoming events.');
  434. }
  435. });
  436. }
  437. , showEvent: function(id) {
  438. this.showProgressBar();
  439. $('li.active', this.$navigation).removeClass('active');
  440. var event = new App.Models.Event({ 'id': id })
  441. , view = new App.Views.Event({
  442. model: event
  443. , tweetsCollection: new App.Collections.EventTweets([], { 'eventId': id })
  444. , photosCollection: new App.Collections.EventPhotos([], { 'eventId': id })
  445. , videosCollection: new App.Collections.EventVideos([], { 'eventId': id })
  446. })
  447. , self = this
  448. ;
  449. event.fetch({
  450. success: function() {
  451. self.hideAndEmptyContainer(function(){
  452. document.title = event.get('name') + ' :: Twalks';
  453. self.displayContainer(view.render().el);
  454. });
  455. }
  456. , error: function(){
  457. alertMessage('warning', 'We encountered an error fetching this event.');
  458. }
  459. });
  460. }
  461. , showProgressBar: function() {
  462. $('li.user', this.$secondaryNav).stop().hide();
  463. $('li.progress', this.$secondaryNav).stop().show();
  464. }
  465. , hideProgressBar: function() {
  466. var progress = $('li.progress', this.$secondaryNav);
  467. if (progress.is(':visible')) {
  468. progress.stop().hide();
  469. $('li.user', this.$secondaryNav).stop().show();
  470. }
  471. }
  472. , hideAndEmptyContainer: function(func) {
  473. var self = this;
  474. this.$container.fadeOut(300, function() {
  475. self.$container.empty();
  476. if (func) { func(); }
  477. });
  478. }
  479. , displayContainer: function(html) {
  480. this.hideProgressBar();
  481. this.$container.append(html);
  482. this.$container.fadeIn(500);
  483. }
  484. });
  485. window.App = App;
  486. String.prototype.parseTweet = function() {
  487. var parsed = this.replace(/[A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&~\?\/.=]+/g, function(url) {
  488. return url.link(url);
  489. });
  490. parsed = parsed.replace(/[@]+[A-Za-z0-9-_]+/g, function(u) {
  491. var username = u.replace("@","")
  492. return u.link("http://twitter.com/" + username);
  493. });
  494. parsed = parsed.replace(/[#]+[A-Za-z0-9-_]+/g, function(t) {
  495. var tag = t.replace("#", "%23")
  496. return t.link("http://search.twitter.com/search?q=" + tag);
  497. });
  498. return parsed;
  499. }
  500. })(jQuery);