Get A Model From A Backbone Collection Without Knowing If The Collection Is Loaded
In working with a client recently, we ran in to a rather sticky situation. We were setting up a route to “persons/:id”, and as you would expect, we wanted to load the person in question and display the details of that person. The trick, though, is that we needed to wait until the persons collection was loaded to be able to retrieve the person from the collection. If we navigate to this route from somewhere else in the application, this isn’t an issue. The persons collection was already loaded and everything goes on normally. If we use a bookmark to get to this url directly, though, then there was no guarantee that the persons collection was loaded because we had not previously run any code to load the collection.
Depending on how the application is architected, and when the persons collection is expected to be loaded, there are a few options that I can see for solving this problem.
Option: Fetch The Model From The Server
The most basic option, and probably the easiest to deal with, is just to fetch the model from the server based on the id parameter that you get from the route.
In this case you just need to create a new model instance, set the `id` attribute on the model directly and then call `.fetch` on the model. It will make the trip to the server to get the data. You can either listen to a “change” event on the model or provide a “success” callback in the fetch method, to know when the data has been returned so that you can load it in to your view and display it as needed.
The major problem here is that you have not loaded the persons collection at all. If the persons collection is expected to be loaded because it’s being used for something related to displaying or working with the individual person model, then this option might not work for you. You could run some additional code to load the collection separately (asynchronously, since it works that way by default). This might help get around any potential issues of needing the collection.
Option: Use The Collection’s “reset” Event
Another easy option may be to load the collection when the request for the route is made. You would set up a new collection instance, bind a callback function to the collection’s “reset” event, and then call `.fetch` on the collection. The callback method would be responsible for retrieving the specific model from the collection and then creating and displaying the view.
There are some potential issues with this solution, though. If you already have the persons collection loaded, then you’re going to load it a second time just to get the one model from it. To mitigate this problem, you would need two different entry points in to the display of the person: one for when you hit the route directly through a bookmark, and one for when the user is already in the app and navigates to the person display through other means.
Having two different entry points in to this part of the app may not be a bad idea. This largely depends on how the application is architected. You wouldn’t want to duplicate all of the code that sets up the display of the person’s details in both of the entry points, but you wouldn’t want to have a bunch of ugly if-statements in that code to determine how to set things up, either. Some simple abstractions of the common bits would help keep this code manageable.
Option: Building An “onReset” Callback
FYI – I have an updated version of this code available in my Backbone.Marionette plugin, as Backbone.Marionette.Callbacks. It reduces the code and complexity significantly, and also eliminates the race condition issues that I mention below. Be sure to use that Callbacks object instead of the code I’ve listed here.
The third option that I can think of – and the one that I implemented for this particular client project – is a variation of using the collection’s reset event. The idea is to build an “onReset” callback system that is aware of whether or not the collection has already been loaded or is still waiting to be loaded.
If you have the persons collection being loaded from some other application initialization code, then you don’t necessarily have the ability to use a simple reset event as shown above. You could try to use the reset event, but there’s a race condition that is introduced in low-latency, high speed networks (i.e. your local development machine).
If you don’t control when the `.fetch` method is called, then you may end up binding to the reset event after the collection has already been reset. In that scenario – which is very likely to happen when working in a local development environment – your view for the specific person model will never get displayed.
The solution I came up with is to have a callback mechanism built in to the collection, that pays attention to the collection’s reset event and knows to either wait for the reset event to be fired, or to fire the callbacks immediately because the collection has already been loaded. I’m calling this an “onReset” callback, for lack of a better description at this point.
The code to use the onReset callbacks would look something like this:
[gist id=1730803 3.js]
In this setup, adding an onReset callback guarantees the callback’s execution. If the collection has not yet been loaded, then it stores the callback and waits for the reset event to fire. If the reset event has already been fired, then it simply executes the callback immediately. Either way, your callback will be executed and you will have the collection available when it does.
Race Condition Reduced. Eliminated?
Here’s the implementation for the onReset code. It’s generally functional and I haven’t yet run in to any problems, yet.
You can then extend from OnResetCollection instead of Backbone.Collection to get this functionality.