Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Single Page Web Apps with Backbone.js (sendhub.com)
229 points by ashrust on March 15, 2012 | hide | past | favorite | 51 comments


I built http://plunker.no.de/edit using Backbone.js and really have enjoyed the process. The addition of the sync event has really helped me out.

That being said, I think the most important abstraction that I introduced in this project and not in others was inspired by moviepilot.com's Chaplin project: https://github.com/moviepilot/chaplin

The revelation was to have a central mediator pub/sub mechanism. I used that central mechanism to allow different parts of the ui to declare intents. Each intent has a target that can handle that intent and (if appropriate) emit a corresponding event. The idea is that there is a single handler of each 'intent' and can be many handlers of 'event's. This is because each UI component doesn't necessarily know of the final receiver of the intent events. Keeps things nicely decoupled (so far at least!).

For example: https://github.com/ggoodman/stsh/blob/master/assets/js/views...

The Sidebar contains a list of filenames in the current 'plunk'. When the user clicks on a filename, an 'intent:activate' event is fired. When the user double-clicks an 'intent:rename' event is fired. Only once those changes have been handled and refired as 'event:activate' or 'event:rename' are they reflected in the UI.


Off-topic but this single comment has generated more HN traffic than posting an actual submission about my site. Go figure.

:-D


What does intent:X do besides firing event:X? i.e. what does intent:X buy you?


That's a good question. The Sidebar example should illustrate this pretty well.

Current scenario:

1. The user clicks the remove file button [-] at the bottom of the Sidebar.

2. The Sidebar fires an 'intent:fileRemove' event through the mediator.

3. The handler of this event can then determine which file should be deleted and can also prompt the user to confirm their intentions.

4. If the user confirms that they want to delete, then the model is changed and the 'event:fileRemove' event is fired off.

5. The UI then reacts to this event.

If the Sidebar instead fired of an 'event:fileRemove' directly this would lead to some issues:

* The Sidebar would need to take over the responsibility of prompting the user about file deletion.

* If I add a new UI entry-point to file removal, this user prompting behaviour needs to be duplicated.

TLDR; It buys me extra decoupling between user intentions (UI -> Model) and state changes (Model/Controller -> UI). It lets me add a Menu with a Remove item without needing to change my controller and model to accommodate it.


A good example for using the dispatch object was how views interact when we send messages out from the compose message form on Sendhub.com. When a user composes a message and submits the form (ComposeMessageFormWidget), we render the new message in the ThreadView, which is a different view object. The ThreadView object listens for new messages to be sent during render

Another, less complicated, way of achieving this is to have the view listen for model attribute changes.

Presumably the compose form generates a Message model which is part of a Messages collection. Both ComposeView and ThreadView can have access to (and listen on) this Messages collection.

Then when a Message is successfully created server-side (or when it's created client-side), a "saved" attribute on the Message model changes to true. This bubbles up through the Messages collection and ThreadView's listener gets tripped with the relevant Message as an argument.


What I still haven't figured out is the best way to do responsive web apps with Backbone.js. Off the top of my head I can only think of two, Flow and AudioVroom. Flow uses separate javascripts for both versions. AudioVroom, has one file, but it's minified and I haven't had a chance to trying to reverse engineer it to see how they implemented the different layouts in a mobile, tablet and desktop environment.


We're approaching that same subject now for our development this week. That will probably lead to another blog post ;-) But we are having some luck with Django Compressor and doing CDN hosting. RE:Different layouts...One thing we've tried to do is rely on CSS as much as possible to do the heavy lifting . As for re-using accross mobile and web, you might want to look into putting all of your low level collection and model code into a git submodule or the equivalent so that it can be re-used accross projects.


Don't forget Trello. FogCreek posted an article about it a while ago, although it admittedly doesn't go into much detail.


We ended up doing something similar here with multiple views. Similar, but different. Our app is very desktop like, with basically a layout container with sub-parts that re-render. Instead of a "widget" system, I created a parent-child hierarchy, where parents would bubble child events on up. This way, the router just receives app notifications from a 3rd or 4th level child and can decide to "switch" the main view. The parents cleaned up the kids.

It worked for this app, but I don't think our pattern was particularly useful unless you had to build the kind of complex "pseudo-multiframe" kind of app we were building.

This is why I love the approach of backbone. It's easy to read, and establish new patterns. Our "multiple view" pattern we're using is a little different from what sendhub is doing, but I think it was fairly direct for both of us to get what we want.


Just a stylistic nitpick: the CSS shadow letterpress effect is fine for titles and other places where emphasis is needed, but it's just distracting when applied to large swaths of code.

I tried to leave this comment on their blog but the only options were to comment using Facebook, Yahoo!, AOL, or Hotmail...


So backbone is pretty awesome. I've recently been basing a project on it, and I really dig the simplicity it affords. One thing tho, which I feel it could really help is progressive enhancement, but it seems that the case is not on the official radar. Let me explain a bit what I am looking for, related to a different project:

I am building an informational site for an org run by a committee. This is all volunteer, and there are several people who can do changes to different parts of the page. I know that a CMS is the traditional solution here, but the combination of no plugins to do some of the specifics needed, and the fact that a CMS is too heavy-weight for the rest of the use cases, have me looking at a custom solution. (To be fair, my desire to play with some of the underlying tools I'm using also has me doing this... but whatever its volunteer :) ). So backbone is really nice for this case because:

* The collections and models work great when someone is in a content editing role/mode.

* The data on the page sorted as collections and models work great for things like sorting, sub-searches, etc when JS is enabled.

* For the above use cases, the hash based standard backbone routing is exactly what is needed.

However:

I would also like to have sub pages/content pages dynamically loaded in combination with pushState when available, and different from the apps. When pushState is not available, a new page load is OK, as linkability is important. Essentially I want to keep the page loads with minimal transfer and client side when possible, but the server will build the page outright if content-type application/json is not requested. This is where backbone seems to fall apart a bit. There is not a good router option that will do this and work with the above described has routing as well. It seems that the backbone folks don't want this use case so I am experimenting with building my own router as a plugin.

I'd rather not do that unless A) there is desire from more folks for this and B) there is not something out there already that works within backbone, or nicely along-side it. Any thoughts from the HN community? (also sorry to hijack the thread a bit)


    > I am building an informational site for an org 
    > run by a committee. This is all volunteer [...]
Use a standard CMS. You'll thank yourself later. ;)


and the organization will thank you when you're gone...


I'm working on something similar to a lot of what you're describing with a custom CMS and the dynamic pushState. I'd be interested in hearing more about your approach. If you want to shoot me an email - tgriesser10 at gmail dot com.


If you want a content editing tool that plays nicely with Backbone, check out http://createjs.org/


Great post. Backbone made all of my code faster and simpler to maintain, too, and perhaps more importantly - easier to think about.


Same here! Once we embraced eventing, things became much more simplistic.


It sounds like there needs to be a garbage collector for backbone. Is there any projects out there dealing with 'Zombie' objects?


As an additional note, you'll only run into "zombie event" problems if you have to throw away views and models that are bound together at different times.

If you tend to throw away models and their corresponding views together, then you never have to unbind anything, and it's all GC'd by JavaScript naturally.


Nice. Yeah, I don't know enough about GC and how it does detecting cycles, but I can see how that would fix the problem. As We haven't addressed local caching of data yet, we're still just holding everything in memory. Therefore, for us many of our models never get thrown away. Definitely Something to watch out for.


Add a destroy() method to your views that unbinds any events bound in the initialize() method or other methods and then calls remove() on `this` (for every .on() have a corresponding .off()). Call this .destroy() method instead of remove(). Most of the time this should free up all references so the javascript GC can sweep and clean up old views.

Another option is to reuse the same views and never re-render the same view twice.


Here's what we did:

1) Derive all views from our own BaseView class (which derives from Backbone.View)

2) BaseView's initialize function sets $el.data('view', this)

3) BaseView provides a `dispose` method which with some default logic for event handlers added in a particular way.

4) We overrode jQuery.cleanData to check for the view data on each element and call dispose on them. This ensures every view is cleaned up when it is removed from the DOM via jQuery.

5) Use $.fn.remove and $.fn.detach appropriately.

This works splendidly in practice.


I'm snprbob86's co-founder at Thinkfuse. Here's our actual jQuery cleanData wrapper (in coffeescript): https://gist.github.com/2046371

  jQuery.cleanData = _.wrap jQuery.cleanData, (cleanData, elems) ->
    for elem in elems
      $(elem).data('view')?.dispose()
    cleanData.apply @, arguments
That lets jQuery handle garbage collecting automatically for us whenever the Backbone view's corresponding element is removed from the page. Just make sure your base view constructor sets the 'view' data attribute on it's element.


> initialize function sets $el.data('view', this)

Whoa, you can do that? Huh.


I think the problem has more to do with event bindings and how they cause references between objects in the JS runtime than it does with backbone. However we found that consistency and patterns were a simple enough solution for this problem. It just takes a bit of double checking is all when you are writing code. In comparison, I would say objective-c's retain/release patterns are a much more difficult to understand.


Since you usually only want the listeners attached when a view is actually part of the page's DOM, would it be possible to monitor the DOM for when the view is removed and do cleanup automatically based on a DOM event? (perhaps using the mutation DOM events http://en.wikipedia.org/wiki/DOM_Events)


Maybe, but that would mean you also have a reference from the dom element back to your JS object that was bound in the first place. I've been wrong plenty of times though, would be cool to see someone give it a try.


Extjs solves this by abstracting the dom. When you want to remove a component from its parent, you call the remove() method and it handles all the dom unbinding behind the scenes. You never interact with the dom api directly. Adding content is done by calling the add() method of the parent and passing it a child component instance. It also decouples rendering from component construction. You can have an entire component hierarchy with operational events and fully interactive from the code's perspective, that isn't rendered to the dom at all.


Has anyone seen any good information about using the Chrome Profiles tool (or similar) to track down these 'zombie' objects?


I do this, but I don't do anything special. I just take a snapshot, enter and exit the view, say 5 times, then take another snapshot. Switch over to showing deltas and look for your objects in the list.


I've become a huge backbone.js fan over the last fews months. If your app uses a lot of large forms I'd definitely look at the backbone.modelbinding project: https://github.com/derickbailey/backbone.modelbinding. Also a few days ago I released a plugin from one of my projects that adds data binding support for deeply nested relationships: https://github.com/jwarzech/backbone.modelbinding.nested


Just to present an alternative: I found for apps which don't use many forms the above plugins can be overkill. I used something like the following:

https://gist.github.com/2049308

..to provide simple form-to-model and model-to-form conversions, with appropriate validation. Just decorate the form elements with data-bind="property" and away you go. (I'm using Bootstrap so the validation is tied heavily to that).

(I'm certainly not trying to denigrate the above plugins - obviously different apps have different requirements and as pointed out, if you have many forms then the above plugins make much more sense. The fact that Backbone lets you make these decisions is for me a nice feature).


Nice and simple...I like it. Snippets like these are great to have in the arsenal. Sometimes I end up using the 'heavy' solutions for simple things (just cause I've used them before and its easy to snap in) when I could really get away with rolling a simple solution like you did there.


Good write up - keep them coming! We've done our experiments as well and plan on moving more to this direction.

Would love to hear how you end up dealing with more nested collections and model loading / persistence.


Thanks! RE: Nested collections, we ended up playing around with Backbone relational as an extension to backbone ( https://github.com/PaulUithol/Backbone-relational) to deal with nested and multi relational collections. In short, whenever faced with a more complex relation, we took the easiest path possible which was normally a matter of creating another collection relating two entities. As far as client side persistence, we did not address that. Its on our backlog, but for right now we are just relying on our API to serve up the right subset of data with every call.


> DYNAMIC LOADING > Cons: Although the jury is still out on this, we have concerns that this approach may not scale to very large sites. As an application becomes larger and more templates become required, its easy to see how the main page load may take more time and increases the amount of load on your servers.

By main page load don't you mean first page load? Ideally the browser will cache them as an asset package and that first page load will be a little slower. But how would that put more load on the servers?


That's funny. I'm working on a project where we're specifically using Backbone in order to be able to scale.

We're compiling all our Backbone templates and pushing them to Akamai so that user's around the world can get the JS templates from a local CDN and only need to get JSON data from our main server. It's far easier than trying to modify the app to be able to deploy across multiple datacenters.


Has anyone here tried optimizing initial page load by including the json data passed to various templates as part of the page, rather than making a separate request to get the data?

for example, what i've been doing is using a server side template write out the clientside templates and initial json data used by the clientside templates:

  <script type="text/javascript">
    var viewModel = {{- JSON.stringify(viewModel); }};
  </script>


Yes -- it's highly recommended to bootstrap all of the data you need for an initial load, and avoid any extra HTTP requests. From the FAQ:

http://backbonejs.org/#FAQ-bootstrap


Good post. I'm similarly happy with how Backbone adds a lightweight structure to my project and guides you into doing REST correctly.

I'm using it in a project which uses appcache and localstorage which means return visitors can load the entire app without a single round trip to the server and can even use it offline.


What impact does this have on SEO?

while I enjoy the use experience of fast loading single page apps, if it means it makes a large amount of your content uncrawlable, that seems like a worrisome business tradeoff.


I started to work on a similar project this week. This should be helpful. Thanks for sharing!


Great! Glad to be of help. :-) Do post a comment on the article or send us an email if you have any questions.


No one else has mentioned using a view manager/factory to handle the life cycle of views?

instead of

  var view = new SomeView(options);

  view.render();
we do

  var view = Vm.create('some descriptor', SomeView, options);

  view.render();
The factory/manager can do many things! List out active views, clean up all views at any stage during their life cycle, default cleaning methods etc etc


Agreed. Always keep SRP in mind. In effect, this is IoC for views. Having a separate manager to manage views and their lifetimes is very useful. Need a popup dialog view? Manager handles it. Views have prerequisite views (like sign in, or loading)? Manager handles it.


Good stuff. I like the dispatch idea. I've encountered the need for a way for views to talk to each other as well, though I ended up using another Backbone Model for that.


We've employed this idea in Typecast (typecastapp.com) and it has proven really useful. One thing to watch out for is how you namespace events... Make sure and stick to a consistant format.


Typecast looks gorgeous. If you're interested in getting listed in the example apps on the Backbone homepage, email me a brief description and a 550px-wide screenshot...


Is Backbone.js worth it for interactive web pages that don't require posting to a server?


Yes! But this depends a bit on the use case. The main benefit of Backbone is that it helps you organize your code into separated data model and view/interaction objects, so that you don’t wind up with a giant mess of deeply nested, arbitrarily interconnected, and unmaintainable DOM event handlers. It also has a nice object designed for coordinating browser history/URL views in single-page apps. If any of those components would be helpful to your project, absolutely try using Backbone for it


So gross. Clicking "about" for example (from the main page) loads the about content in a pokey 1.16 seconds, plus drawing time. There's no hint that the content is loading.

While 1.16s is faster than a new page load, it feels crazy slow.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: