Single Page Web Apps with Backbone.js
blog.sendhub.com
blog.sendhub.com
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.
:-D
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.
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.
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. ;)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.
I tried to leave this comment on their blog but the only options were to comment using Facebook, Yahoo!, AOL, or Hotmail...
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.
Another option is to reuse the same views and never re-render the same view twice.
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.
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.Whoa, you can do that? Huh.
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).
Would love to hear how you end up dealing with more nested collections and model loading / persistence.
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?
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.
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>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.
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.
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 etcWhile 1.16s is faster than a new page load, it feels crazy slow.