Backbone-relational.js + Backbone.View(s) - javascript

The question: The documentation is scarce, and I'm something of a noob -- can anyone confirm the proper (assuming there is one) way to bind Backbone.Views to instances of Backbone.RelationalModel (from backbone-relational.js) for updating/rendering to the dom? I've tried a handful of different approaches, based on the normal Model/View binding in Backbone, with little success.
The backstory (/more info):
I'm learning the ropes with Backbone.js, and have had to pick up a lot over the past week. If I'm missing something obvious (which is highly likely -- including the "right" way to handle my problem below), please call me out.
I'm dealing with a mongodb-backed REST interface (that I don't have full control over -- or I would be re-architecting behavior on the server-side) that takes heavy advantage of nested dictionaries, so I've been reading up on how to best represent that in Backbone (while not breaking the great save() + server sync stuff that Backbone provides).
I've seen two options: backbone-relational and ligament.js.
I've started with backbone-relational.js, and have RelationalModels (backbone-relational's replacement for Backbone's standard Model) created for the various dictionaries in the tree that gets handed back by REST interface. The relationships between them are defined, and console logging the JSON from each model (in their respective initialize functions) shows that they're all being called/loaded up correctly off the server on a fetch() command at the overall collection level.
So, that's all great.
Problem: I've got views "listening" for updates on each of those models (and bound functions that should render templates on the dom), and they never "fire" at all (let alone render...). The main view fires on fetch(), no problem, loading the "top level" model and rendering it on the dom -- but the views that represent the "foreign key" models within that "top level" model never do (even though the data is DEFINITELY getting loaded into each model, as evidenced by the console logging on each model mentioned above).
Any insights would be greatly, greatly appreciated.
In direct response to Raynos reply below (thanks Raynos!):
If I defined a base url for the UpperLevelCollection with the UpperLevelModels existing at (UpperLevelCollection url)/(UpperLevelModel id) on the server, how would I map those LowerLevelCollections to dictionary keys within the one JSON dump for each UpperLevelModel from the server-side? In other words, could using collections within models properly handle a data dump from the server like this (obviously very simplified, but gets at the issue) AND properly save/update/sync it back?
[{
"some_key": "Some string",
"labels": ["A","List","Of","Strings"],
"content": [{
"id": "12345"
"another_key": "Some string",
"list": ["A","list","of","strings"],
},{
"id": "67890"
"another_key": "Some string",
"list": ["A","list","of","strings"],
}],
}]

Generally for nested dictionaries I take the following approach
var UpperLevelCollection = Backbone.Collection.extend({
model: UpperLevelModel
}),
UpperLevelModel = Backbone.model.extend({
initialize: function() {
this.nested = new LowerLevelCollection;
}
}),
LowerLevelCollection = Backbone.Collection.extend({
model: LowerLevelModel
}),
LowerLevelModel = Backbone.Model.extend({});
Just nest those collections inside models all the way down.

The problem might be that as you load new data into you ParentModel, your child collection AFAIK is not actually fetched, it's wiped and replaced by a new collection (see Backbone.HasMany.OnChange on line 584 in backbone-relational.js). Thus your own bindings on the child collection are gone.
This is, in my opinion, a weakness with backbone-relational. This behavior should be configurable, with an option where a slower find-and-update-approach is used instead of wipe-and-replace.

Related

How do I manage a Backbone app?

I am trying to wrap my head around Backbone, more specifically how the an application flows throughout it's life. Unfortunately at my job I do not have access (or say for that matter) on how our API is structured. We have many different calls from different time periods with crazy inconsistent structure.
Overriding fetch or sync is not a problem to standaraize the return but what I run into (at the very beginning of my dive in the a Backbone application) is a how to layout the actual code.
Here is my real world example. This page is non-critical and I am trying to re-write it with Backbone. Here is the flow:
Page loads a list of genre types from a call
Clicking on a genre type loads sub genres based off of the genre type (the sub genre type requres a genre code as the parameter)
Clicking on the sub genre type loads all products with that criteria.
I can get pretty far but at some point I feel the code is getting mangled - or doesn't feel natural. Like I am shoving things in.
So my official questions is: How do I manage a Backbone app?
Here is a summary of my though process:
I created a global namespace as one should
var App = App || {};
Okay, lets start with the main application view as all examples show:
App.MainView = Backbone.View.extend({
//this loads the outer stuff
//and creates an instance of the Genre View
});
Alright pretty straightforward, I am going to need a genre model, collection, and view (this applies to sub genre as well)
App.Genre = Backbone.Model.extend();
App.Genres = Backbone.Collection.extend({
url: 'returns a list of genres',
model: App.Genre,
initialize: function() {
this.fetch();
},
fetch: function() {
var self = this;
$.ajax({
url: this.url,
success: function(response) {
**format return**
self.add(formattedArrayOfModels);
}
});
}
});
Now on to the view, the confusing part
App.GenreView = Backbone.View.extend({
el: 'element',//easy enough
tmpl: 'my handlebars template',//implementing handlebars...no problem
initialize: function() {
//this produces a collection full of genres
this.genreList = new App.Genres();
this.genreList.on('add', _.bind(this.render, this));
},
render: function() {
//rendering not a problem, pretty straight forward
}
});
Up until here I have no problems. The genre list loads and we're good to go. So, now when the user clicks a genre I want it to load a sub genre
events: {
'click a': 'getSubGenres'
},
getSubGenres: function(e) {
}
Here is my problem. In getSubGenres do I keep it local?
var subGenre = new App.SubGenreView();
Or should I make it part of the Genre view?
this.subGenre = new App.SubGenreView();
Should I somehow put it in a parent object so it can be accessed by other views? How do I control things like that?
And if I already have a collection of sub genres how do I just use the loaded collection (instead of another ajax call).
Is this the approach you would use?
couple of things before I answer,
first: the fetch function doesn't need an $ajax call since it's its job, so, you can evaluate error:function(){} and success:function(){} immediately inside fetch, but that's assuming that the URL is set correctly.
second: one thing that helped me a lot in my backbone keyboard-head-fight is the addy osmani Backbone Fundamentals which contains a very rich tutorial in pdf format.
now back to the question: from my experience, you will mostly need 'this', so it's a good habbit to get used to it, plus there is something that solves a lot of these issues if implemented correctly: backbone layoutmanager
anyway, the decision of where to place the subview, is totally a design decision in your case and depends a lot on how you structure your page and files.
about how to use the "collection" that is preloaded: I really didn't get it, because the collection you're talking about contains all the subgenres, so usually it shouldn't change even if the view changes to a certain genre view, you are still able to use it.
but still everything I said, is relative to how you structure your files, I do an app.js and a router.js and lots of other files, but the main work is always on the main two, so basically I always get access to everything.
I hope this answered your question

Relating two sets of JSON data in Ember

I'm loading two sets of data separately but I'd like them to be related. Allow me to explain.
Firstly, I'm not using Ember-data but am instead using a simple $.ajax wrapper as outlined in this post from one of the Discourse team.
I have a concept of channels and programmes.
Channels JSON:
[{
"ID":94,
"Name":"BBC1"
},
{
"ID":105,
"Name":"BBC2"
}]
I have to gather the IDs from this JSON to be able to then request the programmes for those channels. So a response from the programmes endpoint will look a bit like this:
Programmes JSON:
{
"Channels": [
{
"Listings": [
{
"Id": "wcy2g",
"Genres": "Education",
"S": "2013-04-26T10:45",
"E": "2013-04-26T11:15",
"T": "Crime Scene Rescue"
}
]
},
{
"Listings": [
{
"Id": "wcwbs",
"Genres": "Current affairs,News",
"S": "2013-04-26T11:00",
"E": "2013-04-26T12:00",
"PID": "nyg",
"T": "Daily Politics"
}
]
}
]
}
Each Listings array can contain x amount of programmes and the objects in the Channels array relate to the order in which they are requested (by the IDs from the Channels.json) so in this case Channels[0] is BBC1 and Channels[1] is BBC2.
What I'd like is to request these two data sets as a single JSON request each but then somehow relate them. So having a channel controller that has x amount of programme models. I also need to render the channels and their programmes in two different templates
Was thinking I could iterate through the channels.json and use the index of the item to look up the relevant items in programmes.json and create the relationship that way.
Not too sure how to use Ember to achieve this though.
Thanks
I did something very similar to this and got it working in ember. I'll sketch out what I did, using your objects. Note that I'm fairly new to ember so a grain of salt may be necessary.
First, you'll want to have model objects for "Channels", "Channel" and "Programme". This will eventually let you have Controllers and Routers for each of those things, matching up nicely with ember's naming conventions. The ChannelsData will have many ChannelData objects in it, and each ChannelData will have many ProgrammeData objects. How do you get these populated?
In your ChannelsRoute class you can have a model() function which returns the model data for that route. Your model function can call create() on ChannelsData to create an instance, and then call a loadAll function on ChannelsData. ChannelsData implements loadAll() using your preferred flavor of ajax. The dead-simple easiest thing to do is to have that function do both of your ajax calls and build the entire tree of data.
You will then find that you'll run into trouble if your ChannelRoute class tries to call its model(), for instance if you enter a path like #/channels/105 directly into the browser. To work around that, make a simple object store of your own on your App object, something like App.ChannelsStore = {}, and when you create each Channel put a reference to it in your ChannelsStore (by id). Then your ChannelRoute.model function can look up its model from that store. But only if ChannelsRoute.model has completed first!
If the user entered that #/channels/105 route as the very first entry into your app, then your code will go through the ChannelsRoute.model() method, and immediately go through the ChannelRoute.model() method, probably before your ajax has completed. In that case you can have the ChannelRoute.model() method create a temporary Channel (with no programmes, for instance) and put that in the App.ChannelsStore. Your logic for building up the whole tree of data should then be willing to check the ChannelsStore to see if an object with a given id already exists, and if so to update it. You end up with something like:
App.ChannelRoute = Ember.Route.extend({
model: function(params) {
var channel = App.ChannelsStore[params.channel_id];
// create stub version if not found
if (!channel) {
channel = App.ChannelData.create({ID: params.channel_id});
App.ChannelsStore[params.channel_id] = channel;
}
return channel;
}
});
(You may end up building a ProgrammeStore similarly, but that's just more of the same.)
The updating of the temporary object actually demonstrates a very cool aspect of ember, which is that your ui may be presented with the values from the temporary object, but then when your ajax call completes and the Channels and Programmes are all loaded - your ui will update properly. Just make sure you update your objects with the set() method, and that your ui templates are happy to work with partial data.

Backbone.js - Sharing collections across multiple routes

I'm working on a Backbone.js application with multiple "sections". Each "section" may have multiple routes but share a single collection. Here's a simple example (with sections "a" and "b"): http://jsfiddle.net/scttnlsn/LW4Ny/
In the example, all the collections are fetched when the router is initialized so that they can be shared across multiple routes without the need to re-fetch in every route handler. This seems fine at first but I am wary of continuing this way when the number of shared collections starts to grow. Additionally, it seems silly to be fetching collections for "sections" that may never even be visited by the user- I would much rather load data on demand.
The obvious alternative is to fetch the data in each route handler instead of when the router is initialized. This would mean that only data that is actually needed is fetched, however, it still ends up performing unnecessary fetches when moving between routes in the same "section". There would no longer be any "sharing" of collection data.
What's a good way to handle this situation? I feel like I need to implement some sort of cache-like structure. Are there existing solutions?
Thanks!
-Scott
Assuming I understand the issue... you could skip fetching on initialization (as in the alternative you describe), but also skip instantiation of the collection altogether until needed. Then declare a factory(ish) method:
getOrFetchCollection: function(collectionId) {
var collection = this[collectionId];
if(!collection) {
collection = this[collectionId] = new Backbone.Collection();
collection.url = urlFor(collectionId);
collection.fetch();
}
return collection;
}
Then call it from within each route handler:
a1: function() {
var view = new View({ title: 'a1', collection: this.getOrFetchCollection('a') });
show(view);
}

Why is Backbone model sending duplicate attributes to server on save?

I'm writing a practice Backbone app, with Rails backend API, and I'm confused about the behavior of save on Backbone models.
Let's say a Team has many Players, and I want to save a team with numerous players in a single POST.
So in Rails I have:
class Team < ActiveRecord::Base
has_many :players
accepts_nested_attributes_for :players
end
class Player < ActiveRecod::Base
belongs_to :team
end
and for backbone client, I have a Player model and a Players collection defined (not shown)
and then the containing Team model (NOTE: no Teams collection)
Demo.Models.Team = Backbone.Model.extend({
urlRoot: '/teams',
defaults: {
'team_size': 12
},
initialize: function() {
this.players = new Demo.Collections.Players());
},
toJSON: function() {
var json = _.clone(this.attributes);
json.players_attributes = this.players.map(function(player) {
return player.toJSON();
});
return json;
}
}
When I examine my stringified JSON in the browser, everything looks good:
{"team_size":12, "players_attributes":[{"name":"Fred"},{"name":"Jim" },{"name":"Mark"}]}
Checking the server logs, the lone top level attribute ('team size') is repeated, once at the top level, and then repeated under a root key.
Started POST "/teams" for 127.0.0.1 at 2012-06-07 13:39:40 -0400
Processing by TeamsController#create as JSON
Parameters: {
"team_size"=>12, "players_attributes":[{"name":"Fred"},{"name":"Jim" },{"name":"Mark"}]},
"team"=>{"team_size"=>12}
}
I have a few questions:
What's the best way to ensure the player_attributes are nested inside the root key? I (So that I can do a nested save inside TeamController, in the standard rails manner: (i.e. Team.create(params[:team]) ) I can accomplish this with some javascript hackery inside toJSON, but I'm guessing there's an easier, cleaner way.
Is this standard, desirable behaviour? To send duplicates of attributes like this? I guess there's no harm, but it doesn't smell right.
Am I not defining the url / urlRoot correctly or some such?
thanks
1- You have to override the toJSON method in order to include the model name as the root of the JSON element sent to the server.
toJSON: function() {
return { team: _.clone( this.attributes ) }
},
Since you are already messing and overriding this method I don't see any reasons not to go this way.
2- This is a very strange behavior you're describing. Try:
class Team < ActiveRecord::Base
self.include_root_in_json = false
end
It will probably eliminate Rails duplicate params parsing. Another advantage you get from this is that Rails won't include the team as a root element of its generated JSON to the client.
3- Your definition of urlRoot is just fine.
I arrived here while looking for same issue. So even it's an old question I think it's worth giving the answer.
I actually found a Rails setting that explain these duplicate attributes: wrap_parameters
http://apidock.com/rails/v3.2.13/ActionController/ParamsWrapper/ClassMethods/wrap_parameters
Just set it to an empty array, and rails won't try to wrap parameters coming from your JSON requests.
Although you can use the toJSON hack mentioned by others, this is actually not such a good idea. For one, it produces an inconsistent result between sync and save with {patch: true} (this inconsistency is because the sync method calls toJSON if you don't patch, but doesn't call toJSON if you have patch set to true)
Instead, a better solution is to use a patched version of Backbone that overload the sync method itself. The backbone-rails gem does this automatically, or you can pull backbone_rails_sync.js into your own app. A more complete answer to this question can be found here: Backbone.js and Rails - How to handle params from Backbone models?

Modeling non-standard actions in Backbone.js with Models and Collections

I'm working on a project where there's a "reading list", in this list are "reading list items" - they're an abstract concept, on the server-side they can be Books, Articles, etc - they're handled throughout with a decorator which abstracts away the differences, and that works out very well.
I'm working on something that looks more or less like this:
Collection of ReadingListItems (backbone model)
View that represents the #reading_list (backbone meta-view)
Views that represent one ReadingListItem themselves, the bindings on this view interact with the model directly.
window.ReadingListItem = Backbone.Model.extend
url: ->
#get('_links').self
markRead: ->
$.ajax
method: 'put'
url: #get('_links').read_list
markDismissed: ->
$.ajax
method: 'delete'
url: #get('_links').mark_dismissed
This is a model, the view looks something like this:
window.ReadingListView = Backbone.view.extend
el: '#reading_list'
render: ->
#el.empty()
#collection.each (list_item) ->
#el.append( new ReadingListItemView(list_item).render().el() )
window.ReadingListItemView = Backbone.View.extend
events: {
"click .js-mark-read" : "markRead"
"click .js-mark-dismissed" ; "markDismissed"
}
markRead: ->
#model.markRead()
$el.fadeOut
#resetCollection
markDismissed: ->
#model.markDismissed()
$el.fadeOut
#resetCollection
resetCollection: ->
// to trigger a redraw, I really do this:
window.reading_list_collection.reset()
This works (caveat I've written this code for SO without actually running it, so it might be slightly wrong, but it's conceptually what I have right now)
Here are my questions regarding it:
It feels like defining verbs that aren't CRUD on a model seems to violate the backbone "normal usage", and I've not been lucky finding any information about how to handle this (acting on models in ways that isn't destroying them)
How does the collection know if a model had "something" happen to it (when that something isn't create/delete)?
Is there a better way I could model this list?
When the watch-list is too empty, it should refresh itself from the server, it's pre-seeded with ~20 items in JSON, and in fact only displays the first 10. (I might move this all to be JSON, since it isn't much of a performance win to seed the page)
There's something else that happens, that on the markRead / markDismised another view on the page is told to re-download the list from the server, to display a summary counter of the reading list, I'd like if this were bound to the same collection that powers the ReadingListView
Please feel free to comment if anything is unclear, I know that SO doesn't lend itself well to open-ended questions without concrete error messages, but I'm looki

Categories

Resources