From 8e8624073cc27fe377d8417159d69a9f3318cc22 Mon Sep 17 00:00:00 2001 From: Alex Young Date: Mon, 4 Apr 2011 21:19:00 +0100 Subject: [PATCH] Started re-implementing client-side code with Backbone --- app.js | 17 ++++- public/javascripts/application.js | 94 ++++++++++++++++++++++++++++ public/javascripts/backbone-min.js | 27 ++++++++ public/javascripts/underscore-min.js | 26 ++++++++ views/documents/index.jade | 7 +-- views/layout.jade | 5 ++ 6 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 public/javascripts/backbone-min.js create mode 100644 public/javascripts/underscore-min.js diff --git a/app.js b/app.js index 922cef6..9c59061 100644 --- a/app.js +++ b/app.js @@ -183,6 +183,19 @@ if (app.settings.env == 'production') { } // Document list +app.get('/documents', loadUser, function(req, res) { + Document.find({ user_id: req.currentUser.id }, + [], { sort: ['title', 'descending'] }, + function(err, documents) { + documents = documents.map(function(d) { + return { title: d.title, _id: d._id }; + }); + res.render('documents/index.jade', { + locals: { documents: documents, currentUser: req.currentUser } + }); + }); +}); + app.get('/documents.:format?', loadUser, function(req, res) { Document.find({ user_id: req.currentUser.id }, [], { sort: ['title', 'descending'] }, @@ -195,9 +208,7 @@ app.get('/documents.:format?', loadUser, function(req, res) { break; default: - res.render('documents/index.jade', { - locals: { documents: documents, currentUser: req.currentUser } - }); + res.send('Format not available', 400); } }); }); diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 7cf4e61..4caa07e 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -1,5 +1,95 @@ (function() { + var Document, Documents, DocumentRow, DocumentList, DocumentView; + + _.templateSettings = { + interpolate : /\{\{(.+?)\}\}/g + }; + + Document = Backbone.Model.extend({ + Collection: Documents, + + url: function() { + return '/documents/' + this.get('_id') + '.json'; + }, + + display: function() { + this.fetch({ + success: function(model, response) { + $('#editor-container input.title').val(model.get('title')); + $('#editor').val(model.get('data')); + } + }); + } + }); + + Documents = new Backbone.Collection(); + Documents.url = '/documents/titles.json'; + Documents.model = Document; + Documents.comparator = function(d) { + return d.get('title'); + }; + + DocumentView = Backbone.View.extend({ + events: { + }, + + initialize: function() { + }, + }); + + DocumentRow = Backbone.View.extend({ + tagName: 'li', + + events: { + 'click a': 'open' + }, + + template: _.template($('#document-row-template').html()), + + initialize: function() { + _.bindAll(this, 'render'); + }, + + open: function() { + $('#document-list .selected').removeClass('selected'); + $(this.el).addClass('selected'); + this.model.display(); + }, + + render: function() { + $(this.el).html(this.template({ + id: this.model.id, + title: this.model.get('title') + })); + return this; + } + }); + + DocumentList = Backbone.View.extend({ + el: $('#document-list'), + Collection: Documents, + + initialize: function() { + _.bindAll(this, 'render'); + this.Collection.bind('refresh', this.render); + }, + + render: function(documents) { + var element = this.el; + documents.each(function(d) { + d.rowView = new DocumentRow({ model: d }); + element.append(d.rowView.render().el); + }); + } + }); + + new DocumentList(); + window.Documents = Documents; + + //Documents.fetch(); + // Easily get an item's database ID based on an id attribute + /* $.fn.itemID = function() { try { var items = $(this).attr('id').split('-'); @@ -60,6 +150,8 @@ } }); + */ + // Correct widths and heights based on window size function resize() { var height = $(window).height() - $('#header').height() - 1, @@ -92,6 +184,7 @@ }); } + /* $('#document-list li a').live('click', function(e) { var li = $(this); @@ -142,6 +235,7 @@ }); } }); + */ function hideFlashMessages() { $(this).fadeOut(); diff --git a/public/javascripts/backbone-min.js b/public/javascripts/backbone-min.js new file mode 100644 index 0000000..161b401 --- /dev/null +++ b/public/javascripts/backbone-min.js @@ -0,0 +1,27 @@ +// Backbone.js 0.3.3 +// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://documentcloud.github.com/backbone +(function(){var e;e=typeof exports!=="undefined"?exports:this.Backbone={};e.VERSION="0.3.3";var f=this._;if(!f&&typeof require!=="undefined")f=require("underscore")._;var h=this.jQuery||this.Zepto;e.emulateHTTP=false;e.emulateJSON=false;e.Events={bind:function(a,b){this._callbacks||(this._callbacks={});(this._callbacks[a]||(this._callbacks[a]=[])).push(b);return this},unbind:function(a,b){var c;if(a){if(c=this._callbacks)if(b){c=c[a];if(!c)return this;for(var d=0,g=c.length;d/g,">").replace(/"/g, +""")},set:function(a,b){b||(b={});if(!a)return this;if(a.attributes)a=a.attributes;var c=this.attributes,d=this._escapedAttributes;if(!b.silent&&this.validate&&!this._performValidation(a,b))return false;if("id"in a)this.id=a.id;for(var g in a){var i=a[g];if(!f.isEqual(c[g],i)){c[g]=i;delete d[g];if(!b.silent){this._changed=true;this.trigger("change:"+g,this,i,b)}}}!b.silent&&this._changed&&this.change(b);return this},unset:function(a,b){b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&& +!this._performValidation(c,b))return false;delete this.attributes[a];delete this._escapedAttributes[a];if(!b.silent){this._changed=true;this.trigger("change:"+a,this,void 0,b);this.change(b)}return this},clear:function(a){a||(a={});var b=this.attributes,c={};for(attr in b)c[attr]=void 0;if(!a.silent&&this.validate&&!this._performValidation(c,a))return false;this.attributes={};this._escapedAttributes={};if(!a.silent){this._changed=true;for(attr in b)this.trigger("change:"+attr,this,void 0,a);this.change(a)}return this}, +fetch:function(a){a||(a={});var b=this,c=j(a.error,b,a);(this.sync||e.sync)("read",this,function(d){if(!b.set(b.parse(d),a))return false;a.success&&a.success(b,d)},c);return this},save:function(a,b){b||(b={});if(a&&!this.set(a,b))return false;var c=this,d=j(b.error,c,b),g=this.isNew()?"create":"update";(this.sync||e.sync)(g,this,function(i){if(!c.set(c.parse(i),b))return false;b.success&&b.success(c,i)},d);return this},destroy:function(a){a||(a={});var b=this,c=j(a.error,b,a);(this.sync||e.sync)("delete", +this,function(d){b.collection&&b.collection.remove(b);a.success&&a.success(b,d)},c);return this},url:function(){var a=k(this.collection);if(this.isNew())return a;return a+(a.charAt(a.length-1)=="/"?"":"/")+this.id},parse:function(a){return a},clone:function(){return new this.constructor(this)},isNew:function(){return!this.id},change:function(a){this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);this._changed=false},hasChanged:function(a){if(a)return this._previousAttributes[a]!= +this.attributes[a];return this._changed},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=false,d;for(d in a)if(!f.isEqual(b[d],a[d])){c=c||{};c[d]=a[d]}return c},previous:function(a){if(!a||!this._previousAttributes)return null;return this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},_performValidation:function(a,b){var c=this.validate(a);if(c){b.error?b.error(this,c):this.trigger("error",this,c,b);return false}return true}}); +e.Collection=function(a,b){b||(b={});if(b.comparator){this.comparator=b.comparator;delete b.comparator}this._boundOnModelEvent=f.bind(this._onModelEvent,this);this._reset();a&&this.refresh(a,{silent:true});this.initialize(a,b)};f.extend(e.Collection.prototype,e.Events,{model:e.Model,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){if(f.isArray(a))for(var c=0,d=a.length;c').hide().appendTo("body")[0].contentWindow; +"onhashchange"in window&&!a?h(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);return this.loadUrl()},route:function(a,b){this.handlers.push({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();if(a==this.fragment&&this.iframe)a=this.getFragment(this.iframe.location);if(a==this.fragment||a==decodeURIComponent(this.fragment))return false;if(this.iframe)window.location.hash=this.iframe.location.hash=a;this.loadUrl()},loadUrl:function(){var a=this.fragment= +this.getFragment();return f.any(this.handlers,function(b){if(b.route.test(a)){b.callback(a);return true}})},saveLocation:function(a){a=(a||"").replace(l,"");if(this.fragment!=a){window.location.hash=this.fragment=a;if(this.iframe&&a!=this.getFragment(this.iframe.location)){this.iframe.document.open().close();this.iframe.location.hash=a}}}});e.View=function(a){this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize(a)};var q=/^(\w+)\s*(.*)$/;f.extend(e.View.prototype,e.Events, +{tagName:"div",$:function(a){return h(a,this.el)},initialize:function(){},render:function(){return this},remove:function(){h(this.el).remove();return this},make:function(a,b,c){a=document.createElement(a);b&&h(a).attr(b);c&&h(a).html(c);return a},delegateEvents:function(a){if(a||(a=this.events)){h(this.el).unbind();for(var b in a){var c=a[b],d=b.match(q),g=d[1];d=d[2];c=f.bind(this[c],this);d===""?h(this.el).bind(g,c):h(this.el).delegate(d,g,c)}}},_configure:function(a){if(this.options)a=f.extend({}, +this.options,a);if(a.model)this.model=a.model;if(a.collection)this.collection=a.collection;if(a.el)this.el=a.el;if(a.id)this.id=a.id;if(a.className)this.className=a.className;if(a.tagName)this.tagName=a.tagName;this.options=a},_ensureElement:function(){if(!this.el){var a={};if(this.id)a.id=this.id;if(this.className)a["class"]=this.className;this.el=this.make(this.tagName,a)}}});var m=function(a,b){var c=r(this,a,b);c.extend=m;return c};e.Model.extend=e.Collection.extend=e.Controller.extend=e.View.extend= +m;var s={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};e.sync=function(a,b,c,d){var g=s[a];a=a==="create"||a==="update"?JSON.stringify(b.toJSON()):null;b={url:k(b),type:g,contentType:"application/json",data:a,dataType:"json",processData:false,success:c,error:d};if(e.emulateJSON){b.contentType="application/x-www-form-urlencoded";b.processData=true;b.data=a?{model:a}:{}}if(e.emulateHTTP)if(g==="PUT"||g==="DELETE"){if(e.emulateJSON)b.data._method=g;b.type="POST";b.beforeSend=function(i){i.setRequestHeader("X-HTTP-Method-Override", +g)}}h.ajax(b)};var n=function(){},r=function(a,b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){return a.apply(this,arguments)};n.prototype=a.prototype;d.prototype=new n;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},k=function(a){if(!(a&&a.url))throw Error("A 'url' property or function must be specified");return f.isFunction(a.url)?a.url():a.url},j=function(a,b,c){return function(d){a?a(b,d):b.trigger("error",b,d,c)}}})(); diff --git a/public/javascripts/underscore-min.js b/public/javascripts/underscore-min.js new file mode 100644 index 0000000..3de58cd --- /dev/null +++ b/public/javascripts/underscore-min.js @@ -0,0 +1,26 @@ +// Underscore.js 1.1.5 +// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore +(function(){var q=this,D=q._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,E=k.unshift,F=o.toString,m=o.hasOwnProperty,s=k.forEach,t=k.map,u=k.reduce,v=k.reduceRight,w=k.filter,x=k.every,y=k.some,p=k.indexOf,z=k.lastIndexOf;o=Array.isArray;var G=Object.keys,A=Function.prototype.bind,c=function(a){return new l(a)};if(typeof module!=="undefined"&&module.exports){module.exports=c;c._=c}else q._=c;c.VERSION="1.1.5";var j=c.each=c.forEach=function(a,b,d){if(a!=null)if(s&&a.forEach===s)a.forEach(b, +d);else if(c.isNumber(a.length))for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})});return e.value};c.min=function(a,b,d){if(!b&&c.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};j(a,function(f,g,h){g=b?b.call(d,f,g,h):f;gh?1:0}),"value")};c.sortedIndex= +function(a,b,d){d=d||c.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};c.zip=function(){for(var a=i.call(arguments),b=c.max(c.pluck(a,"length")),d=Array(b),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};c.keys=G||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)if(m.call(a, +d))b[b.length]=d;return b};c.values=function(a){return c.map(a,c.identity)};c.functions=c.methods=function(a){return c.filter(c.keys(a),function(b){return c.isFunction(a[b])}).sort()};c.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};c.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)if(a[d]==null)a[d]=b[d]});return a};c.clone=function(a){return c.isArray(a)?a.slice():c.extend({},a)};c.tap=function(a,b){b(a);return a};c.isEqual=function(a, +b){if(a===b)return true;var d=typeof a;if(d!=typeof b)return false;if(a==b)return true;if(!a&&b||a&&!b)return false;if(a._chain)a=a._wrapped;if(b._chain)b=b._wrapped;if(a.isEqual)return a.isEqual(b);if(c.isDate(a)&&c.isDate(b))return a.getTime()===b.getTime();if(c.isNaN(a)&&c.isNaN(b))return false;if(c.isRegExp(a)&&c.isRegExp(b))return a.source===b.source&&a.global===b.global&&a.ignoreCase===b.ignoreCase&&a.multiline===b.multiline;if(d!=="object")return false;if(a.length&&a.length!==b.length)return false; +d=c.keys(a);var e=c.keys(b);if(d.length!=e.length)return false;for(var f in a)if(!(f in b)||!c.isEqual(a[f],b[f]))return false;return true};c.isEmpty=function(a){if(c.isArray(a)||c.isString(a))return a.length===0;for(var b in a)if(m.call(a,b))return false;return true};c.isElement=function(a){return!!(a&&a.nodeType==1)};c.isArray=o||function(a){return F.call(a)==="[object Array]"};c.isArguments=function(a){return!!(a&&m.call(a,"callee"))};c.isFunction=function(a){return!!(a&&a.constructor&&a.call&& +a.apply)};c.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};c.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)};c.isNaN=function(a){return a!==a};c.isBoolean=function(a){return a===true||a===false};c.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};c.isRegExp=function(a){return!!(a&&a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};c.isNull=function(a){return a===null};c.isUndefined=function(a){return a===void 0};c.noConflict=function(){q._= +D;return this};c.identity=function(a){return a};c.times=function(a,b,d){for(var e=0;e/g,interpolate:/<%=([\s\S]+?)%>/g};c.template=function(a,b){var d=c.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate, +function(e,f){return"',"+f.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(e,f){return"');"+f.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return b?d(b):d};var l=function(a){this._wrapped=a};c.prototype=l.prototype;var r=function(a,b){return b?c(a).chain():a},I=function(a,b){l.prototype[a]=function(){var d=i.call(arguments);E.call(d,this._wrapped);return r(b.apply(c, +d),this._chain)}};c.mixin(c);j(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=k[a];l.prototype[a]=function(){b.apply(this._wrapped,arguments);return r(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];l.prototype[a]=function(){return r(b.apply(this._wrapped,arguments),this._chain)}});l.prototype.chain=function(){this._chain=true;return this};l.prototype.value=function(){return this._wrapped}})(); diff --git a/views/documents/index.jade b/views/documents/index.jade index 1230ffe..9f56445 100644 --- a/views/documents/index.jade +++ b/views/documents/index.jade @@ -1,10 +1,8 @@ #left.outline-view #DocumentTitles ul#document-list - - for (var d in documents) - li - a(id='document-title-' + documents[d].id, href='/documents/' + documents[d].id) - =documents[d].title + li#document-row-template(style='display: none') + a(id='document_{{ id }}') {{ title }} ul.toolbar li @@ -28,3 +26,4 @@ ul#controls.toolbar a#save-button(href='#') Save li a#html-button(href='#') HTML + diff --git a/views/layout.jade b/views/layout.jade index 792ddf2..5ca31b9 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -7,6 +7,8 @@ html script(type='text/javascript', src='/javascripts/json.js') script(type='text/javascript', src='https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js') script(type='text/javascript', src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.7/jquery-ui.min.js') + script(type='text/javascript', src='/javascripts/underscore-min.js') + script(type='text/javascript', src='/javascripts/backbone-min.js') body #container #header @@ -23,3 +25,6 @@ html !{flashMessages} != body script(type='text/javascript', src='/javascripts/application.js') + script(type='text/javascript') + Documents.refresh(!{JSON.stringify(documents)}); +