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

/public/javascripts/app.js

https://bitbucket.org/nickolas/mark
JavaScript | 450 lines | 345 code | 84 blank | 21 comment | 10 complexity | 150f14d701e5e273a1ad5b6ae68a056f MD5 | raw file
Possible License(s): MIT
  1. /*
  2. * app.js
  3. *
  4. * A client for the notes api.
  5. */
  6. (function($) {
  7. var app = {
  8. models: {},
  9. collections: {},
  10. views: {},
  11. templates: {},
  12. routers: {}
  13. };
  14. app.templates.notes = _.template('');
  15. app.templates.note = _.template('\
  16. <span class="title">\
  17. <%= title %>\
  18. </span>\
  19. <span class="controls">\
  20. <a href="#" class="destroy">\
  21. <i class="icon-remove"></i>\
  22. </a>\
  23. </span>\
  24. <div class="clear"></div>\
  25. ');
  26. app.templates.editNote = _.template('\
  27. <form>\
  28. <div class="title">\
  29. <input type="text" name="title" value="<%= title %>">\
  30. </div>\
  31. <div class="content">\
  32. <textarea name="content"><%= content %></textarea>\
  33. </div>\
  34. <div class="actions">\
  35. <div class="preview-actions">\
  36. <button class="button preview">\
  37. <i class="icon-share-alt"></i>\
  38. Save &amp; Export\
  39. </button>\
  40. <div id="modal-<%= _id %>" class="reveal-modal xlarge">\
  41. <h2>Export</h2>\
  42. <div class="preview-window">\
  43. </div>\
  44. <div class="actions">\
  45. Download as \
  46. <a href="/notes/<%= _id %>/html" class="button" target="_blank">HTML</a>\
  47. <a href="/notes/<%= _id %>/pdf" class="button" target="_blank">PDF</a>\
  48. <a href="/notes/<%= _id %>/text" class="button" target="_blank">Text</a>\
  49. </div>\
  50. <a class="close-reveal-modal">&#215;</a>\
  51. </div>\
  52. </div>\
  53. <div class="save-actions">\
  54. <button class="button save"><i class="icon-save"></i>Save</button>\
  55. or <a href="#" class="close">cancel</a>\
  56. </div>\
  57. </div>\
  58. </form>\
  59. ');
  60. app.templates.newNote = _.template('\
  61. <form>\
  62. <div class="title">\
  63. <input type="text" name="title" value="<%= title %>">\
  64. </div>\
  65. <div class="content">\
  66. <textarea name="content"><%= content %></textarea>\
  67. </div>\
  68. <div class="actions">\
  69. <div class="preview-actions">\
  70. </div>\
  71. <div class="save-actions">\
  72. <button class="button save"><i class="icon-save"></i>Save</button>\
  73. or <a href="#" class="close">cancel</a>\
  74. </div>\
  75. </div>\
  76. </form>\
  77. ');
  78. // A single note
  79. app.models.Note = Backbone.Model.extend({
  80. defaults: {
  81. title: 'Untitled',
  82. content: ''
  83. },
  84. urlRoot: '/notes',
  85. idAttribute: '_id'
  86. });
  87. // A collection of notes that belong to the user.
  88. app.collections.Notes = Backbone.Collection.extend({
  89. model: app.models.Note,
  90. url: '/notes'
  91. });
  92. // A master view that lists all notes.
  93. app.views.Notes = Backbone.View.extend({
  94. template: app.templates.notes,
  95. tagName: 'ul',
  96. initialize: function(options) {
  97. var self = this;
  98. self.notes = options.notes;
  99. self.notes.on('reset', self.render, self);
  100. self.notes.on('add', function(note) {
  101. var view = new app.views.Note({note: note});
  102. $(self.el).append(view.el);
  103. self.$('>li').tsort('span.title');
  104. });
  105. },
  106. render: function() {
  107. var self = this;
  108. $(self.el).empty();
  109. for (var i = 0, max = self.notes.models.length; i < max; i++) {
  110. var view = new app.views.Note({note: self.notes.models[i]});
  111. $(self.el).append(view.el);
  112. }
  113. $('#app nav').empty();
  114. $('#app nav').html(self.el);
  115. self.delegateEvents();
  116. self.$('>li').tsort('span.title');
  117. }
  118. });
  119. // A view that edits a single note.
  120. app.views.EditNote = Backbone.View.extend({
  121. template: app.templates.editNote,
  122. tagName: 'article',
  123. initialize: function(options) {
  124. var self = this;
  125. self.note = options.note;
  126. self.note.on('destroy', self.unrender, self);
  127. self.note.on('change', self.render, self);
  128. self.render();
  129. },
  130. render: function() {
  131. var self = this;
  132. if (!self.note.get('title').length || self.note.get('title').length < 1) {
  133. self.note.set('title', 'Untitled');
  134. }
  135. $(self.el).html(self.template(self.note.toJSON()));
  136. },
  137. unrender: function() {
  138. var self = this;
  139. self.remove();
  140. self.unbind();
  141. },
  142. events: {
  143. 'click .save': 'save',
  144. 'click .preview': 'preview',
  145. 'click .close': 'close'
  146. },
  147. save: function(e) {
  148. e.preventDefault();
  149. var self = this;
  150. self.$('.button').attr('disabled', 'disabled');
  151. self.note.save({
  152. 'title': self.$('input[name="title"]').val(),
  153. 'content': self.$('textarea[name="content"]').val()
  154. },{
  155. success: function() {
  156. self.$('.button').removeAttr('disabled');
  157. // Success message goes here.
  158. },
  159. error: function() {
  160. self.$('.button').removeAttr('disabled');
  161. // Error message goes here.
  162. }
  163. });
  164. },
  165. preview: function(e) {
  166. e.preventDefault();
  167. var self = this;
  168. self.$('.button').attr('disabled', 'disabled');
  169. self.note.save({
  170. 'title': self.$('input[name="title"]').val(),
  171. 'content': self.$('textarea[name="content"]').val()
  172. }, {
  173. success: function() {
  174. self.$('.button').removeAttr('disabled');
  175. // Success message goes here.
  176. $.ajax({
  177. url: '/notes/'+self.note.get('_id')+'/html?partial=true'
  178. }).done(function(html) {
  179. self.$('.reveal-modal').reveal({
  180. animation: 'fade'
  181. });
  182. self.$('.preview-window').html('<iframe></iframe>');
  183. self.$('.preview-window iframe').contents().find('body').html(html);
  184. });
  185. },
  186. error: function() {
  187. self.$('.button').removeAttr('disabled');
  188. // Error message goes here.
  189. }
  190. });
  191. },
  192. close: function(e) {
  193. e.preventDefault();
  194. var self = this;
  195. self.unrender();
  196. }
  197. });
  198. // A view that creates a single note.
  199. app.views.NewNote = Backbone.View.extend({
  200. template: app.templates.newNote,
  201. tagName: 'article',
  202. initialize: function(options) {
  203. var self = this;
  204. self.note = options.note;
  205. self.notes = options.notes;
  206. self.render();
  207. },
  208. render: function() {
  209. var self = this;
  210. if (!self.note.get('title').length || self.note.get('title').length < 1) {
  211. self.note.set('title', 'Untitled');
  212. }
  213. $(self.el).html(self.template(self.note.toJSON()));
  214. $('#app #main').html(self.el);
  215. },
  216. complete: function() {
  217. var self = this
  218. , view = new app.views.EditNote({note: self.note});
  219. $('#app #main').html(view.el);
  220. },
  221. unrender: function() {
  222. var self = this;
  223. self.remove();
  224. self.unbind();
  225. },
  226. events: {
  227. 'click .save': 'save',
  228. 'click .close': 'close'
  229. },
  230. save: function(e) {
  231. e.preventDefault();
  232. var self = this;
  233. self.$('.button').attr('disabled', 'disabled');
  234. self.note.set('title', self.$('input[name="title"]').val());
  235. self.note.set('content', self.$('textarea[name="content"]').val());
  236. self.notes.create(self.note, {
  237. wait: true,
  238. success: function() {
  239. self.$('.button').removeAttr('disabled');
  240. self.complete();
  241. },
  242. error: function() {
  243. self.$('.button').removeAttr('disabled');
  244. // Error message goes here.
  245. }
  246. });
  247. },
  248. close: function(e) {
  249. e.preventDefault();
  250. var self = this;
  251. self.unrender();
  252. }
  253. });
  254. app.views.Note = Backbone.View.extend({
  255. template: app.templates.note,
  256. tagName: 'li',
  257. initialize: function(options) {
  258. var self = this;
  259. self.note = options.note;
  260. self.note.on('change', self.render, self);
  261. self.note.on('destroy', self.unrender, self);
  262. self.render();
  263. },
  264. render: function() {
  265. var self = this;
  266. if (!self.note.get('title').length || self.note.get('title').length < 1) {
  267. self.note.set('title', 'Untitled');
  268. }
  269. $(self.el).empty();
  270. $(self.el).html(self.template(self.note.toJSON()));
  271. self.delegateEvents();
  272. },
  273. unrender: function() {
  274. var self = this;
  275. self.remove();
  276. self.unbind();
  277. },
  278. events: {
  279. 'click .destroy': 'destroy',
  280. 'click': 'edit'
  281. },
  282. edit: function(e) {
  283. e.preventDefault();
  284. var self = this;
  285. self.childView = new app.views.EditNote({note: self.note});
  286. $('#app #main').html(self.childView.el);
  287. },
  288. destroy: function(e) {
  289. e.preventDefault();
  290. var self = this
  291. , confirmed = window.confirm('Are you sure you want to delete the note "'+self.note.get('title')+'"?');
  292. if (confirmed) {
  293. self.note.destroy({
  294. wait: true,
  295. success: function(model, response) {
  296. // Success message
  297. },
  298. error: function(model, response) {
  299. // Error message
  300. }
  301. });
  302. }
  303. }
  304. });
  305. // A controller that acts as interface between
  306. // note model and note view.
  307. app.routers.Notes = Backbone.Router.extend({
  308. routes: {
  309. "": "index",
  310. "new": "new"
  311. },
  312. initialize: function() {
  313. var self = this;
  314. self.notes = new app.collections.Notes();
  315. self.notes.fetch();
  316. self.el = $('#container');
  317. },
  318. index: function() {
  319. var self = this
  320. , view = new app.views.Notes({notes: self.notes});
  321. },
  322. new: function() {
  323. var self = this
  324. , view = new app.views.NewNote({note: new app.models.Note(), notes: self.notes});
  325. }
  326. });
  327. function filter(element,what) {
  328. var value = $(element).val();
  329. value = value.toLowerCase().replace(/\b[a-z]/g, function(letter) {
  330. return letter.toUpperCase();
  331. });
  332. if(value == '')
  333. $(what+' > li').show();
  334. else{
  335. $(what+' > li:not(:contains(' + value + '))').hide();
  336. $(what+' > li:contains(' + value + ')').show();
  337. }
  338. };
  339. $(document).ready(function() {
  340. // Initialize the routers.
  341. var noteRouter = new app.routers.Notes();
  342. // Start the app.
  343. Backbone.history.start();
  344. $('#search input').change(function() {
  345. filter('#search input', '#aside nav ul');
  346. });
  347. $('#search a').click(function() {
  348. filter('#search input', '#aside nav ul');
  349. });
  350. $('body > header').append('\
  351. <a class="button new logout" href="/logout">\
  352. <i class="icon-signout"></i>\
  353. </a> \
  354. ');
  355. $('body > header').append('\
  356. <a class="button new note" href="#">\
  357. <i class="icon-plus"></i>\
  358. New\
  359. </a>\
  360. ');
  361. $('body > header .new.note').live('click', function(e) {
  362. e.preventDefault();
  363. noteRouter.new();
  364. });
  365. });
  366. })(jQuery);