Trouble with Creating Backbone Models & Collections - javascript

I'm really, really trying to learn Backbone and it's quite a lot for me to wrap my head around (coming from Rails). So I'm trying to code a simple app that just fetches a collection from a Sinatra backend. Right now, the route /schools returns a JSON object that looks like ["One School", "Two School"]. Pretty simple. Unfortunately, the following always returns ReferenceError for me:
School model
(function() {
window.school = Backbone.Model.extend({});
}).call(this);
School collection
(function() {
window.schools = Backbone.Collection.extend({
url: '/schools',
model: window.school
});
}).call(this);
Console
var f = new window.school({name: "temp"});
undefined
f.id();
ReferenceErrror
So simple interactions like this won't work. Also, calling window.schools.fetch() results in a UndefinedObject error. Don't know where I went wrong exactly, but nothing seems to be working. Any help would be awesome!
Edit: The collection & model are written in a closure because its compiled from Coffeescript.

There are two ways of getting the model's id: model.id and model.get('id'). model.id() is not defined so it will give you an error. See http://documentcloud.github.com/backbone/#Model-id.

I've never used Coffeescript, however, I'm getting better at backbone... so I'll give it a try. There are a couple of things that may be going on here. backbone.js is reliant on jquery or zepto, and underscore.js, which use the '$' and '_' as their special variables. That may be causing problems with coffeescript.
You might want to get a sample backbone app running instead of trying it with coffeescript.
As far as the code above, there are a couple of things I think I've spotted:
When you instantiate a model with data, it will have no 'id' (since it's not been synced with the server as per the documentation mentioned above). If the data IS from the server, then include an id: id in the init hash, and model.id will return an id. If you need a unique identifier for a model that has NOT been synced, you can use the 'cid' attribute (which is a local, unique identifier).
Remember, when you 'extend' you are actually building a class, so, unless you've instantiated an instance of the collection, a 'fetch' won't work. You'd need to do:
var collection = new Collection();
collection.fetch();
The reason why 'save()' isn't working is because you've not defined a url for the singular model. You've defined it in the collection, but not the model, so if you try to instantiate a non-collection model, it's got no reference to the restful service.
Hope that helps!

f does not have an id because it has not been saved to the server. Backbone has two unique identifiers for every model : one is the clientid which is created the very moment the model is client side. This is not sent to the server. When a model is saved to the server, Backbone expects it to return the JSON encoded saved model, which of course has an id attribute (which it aquires once it is saved in the database) and updates the local model to match the model data sent by server, thereby creating the id attribute on the client model instance. If your server side model does does not correspond exactly to the client side model then you can override Backbone.sync and Backbone.Model.parse functions to suit your requirements.
window.schools.fetch() fails because window.schools is a Collection class and not an instance. Create a collection instance just the way you created a model instance before you fetch and make sure the rails resource schools is correctly configured to send a json encoded list of school model instances.
Also if you are going with the out-of-the-box implementation of Backbone.sync , you will have to set :
ActiveRecord::Base.include_root_in_json = false

Related

in Ember.js 2.3, how do I compile a hasMany async call into one call in ember instead of several?

I'm upgrading to ember-cli and ember 2.3. Say I have a model called User and a model called Post , and a user ...
posts: DS.hasMany('post', {async:true})
Now, this works the way I expect it to, lazily loading data and not loading posts unless it is required in either the .js or the template. So when I do
{{#each user.posts as |post|}}
{{post.title}}
{{/each}}
I get each post to render its title without a problem. However, in my server logs, I see this:
GET /posts/2
GET /posts/7
GET /posts/13
where the numbers are the post ids. This is to be expected, as when I return a user instance from the server, I return a list of the ids as the parameter 'posts'. So the user instance has:
...
'posts': '2,7,13'
...
in its data.
Now my question is this: way back when, when I used ember-data 1.0 (pre ember-cli and pre ember 1.13), I remember this call being made to the database instead for the same use case:
GET /posts?ids=2&7&13
or something along that line. I can't remember the exact format, but then, I could access the list of ids on the server side using this line of code:
var ids = req.query.ids.toString();
which gave me a comma separated list of ids (in string format). I would then convert this into the sql statement
SELECT * from posts where id in (2,7,13)
This SQL call was interpreted as a manyArray, I think, on the Ember Side and easily behaved as you would expect an Ember Array would.
How can I get this to happen again? I am quite confident that I am missing something and that I don't have to 'hack' ember-data; I would very much like to compress these calls into one instead of having an individual call to the database for each 'post'.
I should also mention that I am not looking to make {async:false} for these calls.
I think the thing you are looking for is coalesceFindRequests, this is a setting on your Adapter to tell Ember to bunch multiple requests that happen in the same runloop into one GET request as you had in the past.
You can see more detail here but essentially all you need to do is add the following to either your ApplicationAdapter (to enable it for all requests for all types) or to your posts adapter (so that it only affects the post requests)
Here is an example if you are using pod structure for your files (which I recommend)
// app/application/adapter.js
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
coalesceFindRequests: true
})

Breeze.js: How to include all related data in payload even if it was sent

I have a list of of data being return from a "standard" HttpGet IQueryable method from an ApiController that implements the Breeze EFContextProvider. When one of the objects references another object that has already been returned in the payload, Breeze gives me an $ref to refer to the object that was already returned.
I want the object with all related objects return explicitly, not a reference with $ref. Also, I'm not using the breeze.js library on the client side; simply making straight calls to the Controller with a web address.
I found this:
Breeze does not replace the Ref: node with its real data
which is the thing I'm looking for, but using Include on the server still doesn't return all of the data.
Any idea on how to "force" Breeze on the server side to include all related data no matter if it was returned and referenced in the payload?
Update 1
Per Steve's answer below I added the following to the BreezeWebApiConfig.RegisterBreezePreStart method in the App_Start folder:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Object;
Compiling and running produces the same output with only the $ref group instead of the full data. I'm sending a request to the server to $expand the collection. Do I need to change the SerializerSettings upon each request to the controller or will adding this to the BreezeWebApiConfig.RegisterBreezePreStart method be enough?
Update 2
I've added a CustomBreezeConfig class per the instructions at the link that Steve added in his answer. I am however using Breeze.WebApi2 so it appears that the BreezeConfig is actually in Breeze.ContextProvider. The code compiles, but I'm still seeing the same $ref for the actual object in JSON.
Do I need to include this CustomerBreezeConfig class in a specific place in my project for Breeze to use it's serializer settings?
Under WebAPI, Breeze uses the Json.NET serializer to turn the results to JSON. You can change the serializer settings (specifically the PreserveObjectReferences setting) to change this behavior.
Breeze configures it's own JSON serializer, so in a Breeze app, you'll need to configure it as described in the Breeze Web API Controller doc.
Note that, if you turn PreserveObjectReferences off, you might also need to configure the ReferenceLoopHandling setting, if you have circular reference in your object graphs (as most of us do).

Should the Router be responsible for network requests? (Backbone.js)

I'm in disagreement with a co-worker about the responsiblities of a Router. Inside of our single page application, it's my impression to have network requests (AJAX) handled via events being triggered (i.e. Backbone.Events), like so:
events : {
'click a#getUsers' : 'updateModels'
}
updateModels: function() {
$.ajax(); or this.Model.fetch();
}
Yet it is his understanding that network requests should be handled via the router on URL change (depending on a click which changes the url like a[href="#getThings"] like so:
var App = Backbone.Router.extend({
routes: {
"" : "main",
"thing" : "getThings"
}
getThings: function() {
this.newView = new NewView();
$.ajax(); // which populates the view with data
}
});
I wanted to know what the backbone reason is for doing network requests.
The vast majority of your AJAX requests should start with a backbone model or collection and be handled via the backbone.sync function. The key methods are model.fetch(), collection.fetch(), and model.save().
Backbone is flexible and only handles a very primitive set of use cases, so there are cases when you need to step outside these boundaries, but any call to $.ajax in a view or a router is a strong code smell of MVC failure.
To address some of your specifics.
Can a view call model.fetch() to load data?
Yes, this is perfectly fine and idiomatic backbone.
Can a view emit an event which will eventually load data?
Yes, this is perfectly fine. This is only necessary beyond just calling model.fetch() as your application grows in complexity and the decoupling that event emitting allows is valuable and warranted. For a todo list, it's usually overkill. For a huge application, this can be a clean approach.
Does every model fetch cause a change in the browser URL?
No. In fact many single page applications simply live at "/" forever. Routing to unique URLs is optional. Some applications lend themselves easily to it, others not so much. Don't equate "the application needs data X" with "the browser URL needs to be X". These are orthogonal. If a user clicks on the view for "Bill Clinton" in a list of presidents and you want to change the URL to "/presidents/clinton", then go ahead and fire a new route and that makes perfect sense, but sometimes you just want some data without changing the URL.
To summarize the responsibilities of the router, I think of it as follows:
Based on the URL, which View is supposed to be displayed?
Also based on the URL, are there model IDs that need to be extracted and instantiated into model instances?
Wire things up and render the view
So a typical router method pseudocode might be:
when the URL matches /presidents/:name, respond as follows
grab the :name parameter and make a President model
instantiate a PresidentView, passing it the model
call presidentView.render() and swap the view's element into the DOM at the appropriate spot in the overall page layout
With Backbone, you should let Backbone be doing most of the ajax requests for you, which will be more often than not triggered from your view, not the router. And I would never use a jQuery ajax request when you could use a Backbone request instead. this.model.fetch() is the right way to get the model. I see the router as a way to orient your page, but the finer details should be left to the views. However, choosing between logic in the router vs. in the views/collections/models is more art than science, and I would take a look at other Backbone examples to get more guidance. I think, in a lot of cases, the network requests are handled in the views because it's easier.
In response to your comment below, whenever data is returned from the server, Backbone uses parse to get the information it needs, so rewriting fetch would be a poor decision. You can overwrite the parse method for your model to get the information that you want, and nothing more. i.e.:
var YourModel = Backbone.Model.extend({
parse: function(response) {
//You can manipulate the object in other ways as well,
//but here's how you'd delete info
delete response.junk;
return response;
}
});
Similarly, every time you do this.model.save(), it uses toJSON() before the model gets sent to the server, which you can also overwrite:
toJSON: function() {
// default -> return _.clone(this.attributes);
return _new code here_;
}

Setting Backbone Marionette model via Global Object

In a Backbone.Marionette application I'm building, we have an authentication which returns a user object, which we in turn stash at App.User (so it's not truly global).
The problem I'm having is that I don't want to make a call to an API endpoint to access the various properties of the returned user object. The specific use case I'm working through right now is that the returned user object contains data about which modules in the app the user is permitted to access (no worries about security, we've clarified that it's OK that the user can spoof a var in their console to gain access to the UI, the services layer will prevent their actions in such an area from being meaningful).
My goal is to avoid a scenario where every time I need access to users.appAccess (a hypothetical array that lists the modules I can access) in order to instantiate it as a model I have to call out to a URL / API endpoint by declaring it in the collection's definition like this:
Entities.Access = Backbone.Collection.extend({
url: 'http://example.com/users/:id/access/',
}
});
Removing the url property from the above code throws an error, and I can pass it a function which returns empty but this doesn't play nice with
var access = new Entities.Access()
access.fetch();
when attempting to pass the fetched collection to a Marionette CollectionView. Should I simply avoid using the fetch() method and keep it otherwise a typical (albeit hack-ish) Backbone collection definition?
Backbone allows you to populate a Backbone collection either as you have (with an empty constructor) or with a collection of data. It sounds like you've already got the data stored in the User object, and you want to push this information to the Entities.Access collection.
var access = new Entities.Access(user.access);
I'm with you, this feels a bit like a hack, but since Backbone doesn't support this nativly there isn't much else you can do. Have a look at Backbone-Relational or supermodel.js. These projects provide better forms of model nesting than the default implementation.

Are there any Backbone.js tutorials that teach ".sync" with the server?

I read many Backbone.js tutorials, but most of them deal with static objects.
Of course, I have data on the server. I want a tutorial that shows how backbone.js can communicate with the server to fetch data, post data, etc.
This is .sync, right? I read the backbone.js documentation, but still fuzzy on how to use this feature.
Or can someone show me an example?
According to: http://documentcloud.github.com/backbone/#Sync
Backbone.sync is the function that Backbone calls every time it
attempts to read or save a model to the server.
But when? Where do I put the function? I don't know how to use it, and the documentation doesn't give any examples. When does the data get loaded into my models? I get to define when...right?
You never really have to look at .sync, unless you plan to overwrite it. For normal uses, you can simply call model.save() whenever you want and that will execute a post or put (depending on whether the record exists already). If you want to get the data from your backend, use collection.fetch()
You'll of course also need to specify a URL, do so through your collection attribute, collection.url
You can override Backbones native sync functionality if you override it:
Backbone.sync = function() {
//Your custom impl here
}
After that this function is called whenever you call a backbone function like .save() on models or .fetch() on collections. You do not have to care about data transport anymore.
I would suggest taking a look into Backbones source and look how the default sync function is implemented. Then create your own or adopt your server to support the native function.
They are not free, but the following screencasts both have a piece on backend work and how to send data to and get data from Backbone.
Tekpub is a 9 part screencast about asp.net MVC3, with the whole 6th part about using backbone to write an admin module to manage productions. it shows all about handling routing in MVC3 and sending & receiving data
Peepcode
http://peepcode.com/products/backbone-js about basic backbone stuff
http://peepcode.com/products/backbone-ii about interactivity
http://peepcode.com/products/backbone-iii about persistance (it's this third one you will need for server connection information).

Categories

Resources