Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Re-wrote the example application.

  • Loading branch information...
commit cadb1b1d9e06dfeb5f3f423337ac7a9c24394b3c 1 parent 64689b8
@miccolis miccolis authored
Showing with 421 additions and 84 deletions.
  1. +9 −2 examples/simple/index.js
  2. +0 −5 examples/simple/models/Foo.bones
  3. +0 −6 examples/simple/models/Foo.server.bones
  4. +5 −0 examples/simple/models/Project.bones
  5. +58 −0 examples/simple/models/Project.server.bones
  6. +90 −0 examples/simple/routers/App.bones
  7. +43 −0 examples/simple/routers/App.server.bones
  8. +0 −3  examples/simple/routers/Default.bones
  9. +0 −16 examples/simple/routers/Home.bones
  10. +0 −3  examples/simple/routers/Other.server.bones
  11. +3 −0  examples/simple/templates/About._
  12. +20 −0 examples/simple/templates/App._
  13. +5 −0 examples/simple/templates/Error._
  14. +9 −19 examples/simple/templates/Home._
  15. +7 −0 examples/simple/templates/Project._
  16. +6 −0 examples/simple/views/About.bones
  17. +89 −0 examples/simple/views/App.bones
  18. +6 −0 examples/simple/views/App.server.bones
  19. +20 −0 examples/simple/views/Error.bones
  20. +0 −11 examples/simple/views/Foo.bones
  21. +0 −6 examples/simple/views/Foo.server.bones
  22. +6 −7 examples/simple/views/Home.bones
  23. +32 −0 examples/simple/views/Main.bones
  24. +13 −0 examples/simple/views/Project.bones
  25. +0 −6 examples/simple/views/home.server.bones
View
11 examples/simple/index.js
@@ -1,6 +1,13 @@
#!/usr/bin/env node
-require('bones').load(__dirname);
+
+var bones = require('bones')
+
+// Bones load views/models/etc.. in alphabetical order. To explicity load
+// certain ones early simply require them here.
+require('./views/Main');
+
+bones.load(__dirname);
if (!module.parent) {
- require('bones').start();
+ bones.start();
}
View
5 examples/simple/models/Foo.bones
@@ -1,5 +0,0 @@
-model = Backbone.Model.extend({
- sync: function(method, model, options) {
- // client side sync method
- }
-});
View
6 examples/simple/models/Foo.server.bones
@@ -1,6 +0,0 @@
-model = models['Foo'].augment({
- sync: function(method, model, options) {
- model.set({ method: method });
- options.success(model);
- }
-});
View
5 examples/simple/models/Project.bones
@@ -0,0 +1,5 @@
+model = Backbone.Model.extend({
+ url: function() {
+ return '/api/Project/' + encodeURIComponent(this.get('id'));
+ }
+});
View
58 examples/simple/models/Project.server.bones
@@ -0,0 +1,58 @@
+var fs = require('fs'),
+ path = require('path');
+
+/**
+ * The only method we need to override for on the server is `sync` which is
+ * called to load the resource. By default Backbone models will attempt to
+ * retrieve their state from a URI returned from their 'URL' method. Clearly
+ * that won't work on the server, as we're trying to power that very same
+ * URI. Here we do the hard work!
+ */
+models.Project.prototype.sync = function(method, model, options) {
+ // Project data can only be read, so return an error it the client is
+ // trying to do anything else.
+ if (method != 'read') return options.error('Unsupported method');
+
+ var projectDir = path.dirname(require.resolve('bones')) + '/node_modules/' + model.id,
+ resp = {id: model.id};
+
+ var fetchPackage = function(callback) {
+ fs.readFile(projectDir +'/package.json', 'utf8', function(err, data) {
+ if (err) return callback('Could not retrieve project information.');
+ data = JSON.parse(data);
+ resp = _.extend(resp, data);
+ callback();
+ });
+ };
+
+ var fetchReadme = function(callback) {
+ // Setup the regex we'll use for detecting README files.
+ var re = /^readme(|\.\w*)$/i;
+
+ // Scan the project directory, and send a readme back to the client.
+ fs.readdir(projectDir, function(err, files) {
+ if (err) return callback('Project not found.');
+
+ for (var i = 0; i < files.length; i++) {
+ var match = re.exec(files[i]);
+ if (match) {
+ // Found a README! Send it out!
+ fs.readFile(projectDir +'/'+ match.input, 'utf8', function(err, data) {
+ if (err) return callback('Could not retrieve project information.');
+ resp.readme = data;
+ callback();
+ });
+ break;
+ }
+ }
+ });
+ }
+
+ fetchPackage(function(err) {
+ if (err) return options.error(err);
+ fetchReadme(function(err) {
+ if (err) return options.error(err);
+ options.success(resp);
+ });
+ });
+};
View
90 examples/simple/routers/App.bones
@@ -0,0 +1,90 @@
+router = Backbone.Router.extend({
+ routes: {
+ '/': 'home',
+ '/about': 'about',
+ '/project/:project': 'project'
+ },
+
+ // The `home` route...
+ home: function() {
+ var router = this,
+ fetcher = this.fetcher(),
+ projects = new models.Projects();
+
+ fetcher.push(projects);
+ fetcher.fetch(function(err) {
+ if (err) return router.error(err);
+ router.send(views.Home, {collection: projects});
+ });
+ },
+
+ // The `About` route is completely static. It only needs to pass the proper
+ // view (views.About) to be run.
+ about: function() {
+ this.send(views.About);
+ },
+
+ // The 'project' route needs to load a particular project.
+ project: function(project) {
+ var router = this,
+ fetcher = this.fetcher(),
+ project = new models.Project({id: project});
+
+ fetcher.push(project);
+ fetcher.fetch(function(err) {
+ if (err) return router.error(err);
+ router.send(views.Project, {model: project});
+ });
+ },
+
+ // Helper to assemble the page title.
+ pageTitle: function(view) {
+ var title = 'What is Bones?';
+ return (view.pageTitle ? view.pageTitle + ' | ' + title : title);
+ },
+
+ // The send method is...
+ send: function(view) {
+ var options = (arguments.length > 1 ? arguments[1] : {});
+ var v = new view(options);
+
+ // Populate the #page div with the main view.
+ $('#page').empty().append(v.el);
+
+ // TODO explain this!
+ v.render().attach().activeLinks().scrollTop();
+
+ // Set the page title.
+ document.title = this.pageTitle(v);
+ },
+
+ // Generic error handling for our Router.
+ error: function(error) {
+ this.send(views.Error, _.isArray(error) ? error.shift() : error);
+ },
+
+ // Helper to fetch a set of models/collections in parrellel.
+ fetcher: function() {
+ var models = [];
+
+ return {
+ push: function(item) { models.push(item) },
+ fetch: function(callback) {
+ if (!models.length) return callback();
+ var errors = [];
+ var _done = _.after(models.length, function() {
+ callback(errors.length ? errors : null);
+ });
+ _.each(models, function(model) {
+ model.fetch({
+ success: _done,
+ error: function(error) {
+ errors.push(error);
+ _done();
+ }
+ });
+ });
+ }
+ }
+ }
+});
View
43 examples/simple/routers/App.server.bones
@@ -0,0 +1,43 @@
+// Setup a closure variable whice records the time that the server was started.
+// This is used to make sure static resources (css, js) have a different url
+// when the code underneath them changes.
+var time = Date.now();
+
+/**
+ * On the server the send method is overridden to provide the path to actually
+ * send rendered pages to the browser. In addition to a rendered page, we also
+ * append aJSON versions of any models/collections to the page that were used.
+ * to construct it. While duplicative this allow us to easily re-attach those
+ * same views back onto the DOM client side.
+ */
+routers.App.prototype.send = function(view, options) {
+ var options = arguments.length > 1 ? arguments[1] : {};
+
+ // Execute the main view.
+ var main = new view(options);
+ main.render();
+
+ // Provide all models with the data that well be used to prop them back up
+ // on the browser.
+ var o = '{el: $("#main"),';
+ _.each(options, function(v, k) {
+ // Any options that is a model or collection will have it's title
+ // declared. Use this to re-hydrate it.
+ if (v.constructor.title != undefined) {
+ o += JSON.stringify(k) + ': new models.'+ v.constructor.title +'('+ JSON.stringify(options[k]) + '),';
+ } else {
+ o += JSON.stringify(k) + ':' + JSON.stringify(options[k]) +',';
+ }
+ });
+ o = o.replace(/,$/, '}');
+
+ // Finally send the page to the client.
+ this.res.send(Bones.plugin.templates.App({
+ version: time,
+ title: this.pageTitle(main),
+ main: $(main.el).html(),
+ startup: 'Bones.initialize(function(models, views, routers, templates) {'+
+ 'new views.' + main.constructor.title +'('+ o +').attach().activeLinks().scrollTop()'+
+ '});'
+ }));
+};
View
3  examples/simple/routers/Default.bones
@@ -1,3 +0,0 @@
-router = Backbone.Router.extend({
-
-});
View
16 examples/simple/routers/Home.bones
@@ -1,16 +0,0 @@
-router = Backbone.Router.extend({
- routes: {
- '/': 'home',
- '/foo': 'foo'
- },
-
- home: function() {
- var view = new views['Home'];
- if (this.res) this.res.send(view.el);
- },
-
- foo: function() {
- var view = new views['Foo'];
- if (this.res) this.res.send(view.el);
- }
-});
View
3  examples/simple/routers/Other.server.bones
@@ -1,3 +0,0 @@
-router = Backbone.Router.extend({
-
-});
View
3  examples/simple/templates/About._
@@ -0,0 +1,3 @@
+<h1>About this</h1>
+
+<p>Quick explanation of the various views/models/templates/routers in this project.</p>
View
20 examples/simple/templates/App._
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title><%= title %></title>
+ <script src="/assets/bones/all.js?<%= version %>" type="text/javascript" charset="utf-8"></script>
+ </head>
+ <body>
+ <div id='header'>
+ <ul>
+ <li><a href='/'>Home</a></li>
+ <li><a href='https://github.com/developmentseed/bones'>Bones@Github</a></li>
+ </ul>
+ </div>
+ <div id='page' class='limiter clearfix'>
+ <div id='main'><%= main %></div>
+ </div>
+ <script type="text/javascript"><%= startup %></script>
+ </body>
+</html>
View
5 examples/simple/templates/Error._
@@ -0,0 +1,5 @@
+<div class='error-page limiter'>
+ <h2><%= status %></h2>
+ <div class='message'><%= message %></div>
+ <a href='/' class='button'>Return to homepage</a>
+</div>
View
28 examples/simple/templates/Home._
@@ -1,19 +1,9 @@
-<!DOCTYPE html>
-<html>
- <head>
- <meta charset='utf-8'>
- <script src="/assets/bones/vendor.js" type="text/javascript" charset="utf-8"></script>
- <script src="/assets/bones/core.js" type="text/javascript" charset="utf-8"></script>
- <script src="/assets/bones/views.js" type="text/javascript" charset="utf-8"></script>
- <script src="/assets/bones/routers.js" type="text/javascript" charset="utf-8"></script>
- <script src="/assets/bones/models.js" type="text/javascript" charset="utf-8"></script>
- <script src="/assets/simple/app.js" type="text/javascript" charset="utf-8"></script>
- </head>
- <body>
- <div id="view">
- home
- <a href="/">go to home</a>
- <a href="/foo">go to foo</a>
- </div>
- </body>
-</html>
+<h1>What is Bones?</h1>
+<p>Bones is a helper library that allows you to run the excellent Backbone.js library on your server. This example application is designed to demonstrate how bones works, and introduce you to the other excellent libraries that it uses.</p>
+
+<h2>Excellent Libraries</h2>
+<ul>
+ <% _.each(projects, function(v) { %>
+ <li><a href='/project/<%= v %>'><%= v %></a></li>
+ <% }); %>
+</ul>
View
7 examples/simple/templates/Project._
@@ -0,0 +1,7 @@
+<h1><%= name %></h1>
+
+<p><%= description %></p>
+
+<pre>
+<%= readme %>
+</pre>
View
6 examples/simple/views/About.bones
@@ -0,0 +1,6 @@
+view = views.Main.extend({
+ render: function() {
+ $(this.el).empty().append(templates.About());
+ return this;
+ }
+});
View
89 examples/simple/views/App.bones
@@ -0,0 +1,89 @@
+// Starts routing on client
+// ------------------------
+var start = _.once(function() {
+ var bypass = true,
+ _loadUrl = Backbone.History.prototype.loadUrl;
+
+ Backbone.History.prototype.loadUrl = function(e) {
+ if (bypass) {
+ bypass = false;
+ return;
+ }
+ _loadUrl.call(this, e);
+ }
+
+ Bones.start({pushState: true, root: ""});
+});
+
+// Sets up key tracking on client
+// ------------------------------
+// TODO: should we use Bones.currentKeys?
+var keyTracking = _.once(function() {
+ $(function() {
+ // Global tracking of pressed keys.
+ $(document).keydown(function(ev) {
+ window.currentKeys = window.currentKeys || {};
+ window.currentKeys[ev.keyCode] = ev;
+ });
+ $(document).keyup(function(ev) {
+ window.currentKeys = window.currentKeys || {};
+ if (window.currentKeys[ev.keyCode]) {
+ delete window.currentKeys[ev.keyCode];
+ }
+ });
+
+ });
+});
+
+// Topmost view
+// ------------
+view = Backbone.View.extend({
+ _ensureElement: function() {
+ this.el = $('body');
+ },
+ initialize: function() {
+ if (!Bones.server) {
+ feedbackSetup();
+ searchSetup();
+ adminSetup();
+ keyTracking();
+ }
+ }
+});
+
+// Registers event handler for all click events
+// --------------------------------------------
+view.prototype.events = {
+ 'click a': 'routeClick'
+};
+
+// Routes a click event
+// --------------------
+view.prototype.routeClick = function(ev) {
+ if (_.size(window.currentKeys)) {
+ return true;
+ }
+ // We only route client side if the browser supports push state.
+ // The check here is borrowed from Backbone.
+ if (window.history && window.history.pushState) {
+ var href = $(ev.currentTarget).get(0).getAttribute('href', 2);
+ if (href) return view.route($(ev.currentTarget).get(0).getAttribute('href', 2));
+ }
+ return true;
+};
+
+// Routes a path
+// -------------
+view.route = function(path) {
+ start();
+ if (path.charAt(0) === '/') {
+ var matched = _.any(Backbone.history.handlers, function(handler) {
+ if (handler.route.test(path)) {
+ Backbone.history.navigate(path, true);
+ return true;
+ }
+ });
+ return !matched;
+ }
+ return true;
+};
View
6 examples/simple/views/App.server.bones
@@ -0,0 +1,6 @@
+// On the client we override this, but on the server we need the default
+// behavior.
+// TODO explain better...
+views.App.prototype._ensureElement = function() {
+ Backbone.View.prototype._ensureElement.apply(this, arguments);
+};
View
20 examples/simple/views/Error.bones
@@ -0,0 +1,20 @@
+view = views.Main.extend({
+ initialize: function(options) {
+ _.bindAll(this, 'render');
+ options = options || {};
+ // TODO: Push responseText parsing all the way up into Bones.
+ if (options.responseText) {
+ try {
+ options = $.parseJSON(options.responseText);
+ } catch (err) {}
+ }
+ this.options = {};
+ this.options.status = options.status || '404';
+ this.options.message = options.message || 'Not found';
+ views.Main.prototype.initialize.call(this, options);
+ },
+ render: function() {
+ $(this.el).empty().append(templates.Error(this.options));
+ return this;
+ }
+});
View
11 examples/simple/views/Foo.bones
@@ -1,11 +0,0 @@
-view = Backbone.View.extend({
- initialize: function(options) {
- _.bindAll(this, 'render');
- this.render();
- this.trigger('attach');
- },
- render: function() {
- console.log('home view');
- return this;
- }
-});
View
6 examples/simple/views/Foo.server.bones
@@ -1,6 +0,0 @@
-views['Foo'].augment({
- render: function(parent) {
- this.el = templates['Home']();
- return this;
- }
-});
View
13 examples/simple/views/Home.bones
@@ -1,11 +1,10 @@
-view = Backbone.View.extend({
- initialize: function(options) {
- _.bindAll(this, 'render');
- this.render();
- this.trigger('attach');
- },
+view = views.Main.extend({
render: function() {
- console.log('home view');
+ var projects = [];
+ this.collection.each(function(item) {
+ projects.push(item.escape('id'));
+ });
+ $(this.el).empty().append(templates.Home({projects:projects}));
return this;
}
});
View
32 examples/simple/views/Main.bones
@@ -0,0 +1,32 @@
+view = Backbone.View.extend({
+ id: 'main',
+ attach: function() {
+ return this;
+ },
+ // Scrolls to top or fragment
+ // --------------------------
+ scrollTop: function() {
+ var offset = $(window.location.hash).offset();
+ var top = offset ? offset.top : 0;
+ // Scroll top FF, IE, Chrome safe
+ if ($('body').scrollTop(0)) {
+ $('body').scrollTop(top);
+ return this;
+ }
+ if ($('html').scrollTop(0)) {
+ $('html').scrollTop(top);
+ }
+ return this;
+ },
+ activeLinks: function() {
+ var activePath = window.location.pathname;
+ $('a.active').removeClass('active');
+ $('a.exact').each(function(i, a) {
+ activePath == $(a).attr('href') && $(a).addClass('active');
+ });
+ $('a:not(.exact)').each(function(i, a) {
+ (activePath.indexOf($(a).attr('href')) == 0) && $(a).addClass('active');
+ });
+ return this;
+ }
+});
View
13 examples/simple/views/Project.bones
@@ -0,0 +1,13 @@
+view = views.Main.extend({
+ render: function() {
+ // TODO Format links in the readme as link.
+ // TODO avoid double encoding issues.
+
+ $(this.el).empty().append(templates.Project({
+ name: this.model.escape('name'),
+ description: this.model.escape('description'),
+ readme: this.model.escape('readme')
+ }));
+ return this;
+ }
+});
View
6 examples/simple/views/home.server.bones
@@ -1,6 +0,0 @@
-views['Home'].augment({
- render: function(parent) {
- this.el = templates['Home']();
- return this;
- }
-});
Please sign in to comment.
Something went wrong with that request. Please try again.