Is there a way to built more complex Model in Backbone.js, let me explain by an example :
This is a Json Session object :
{
id: "17",
notes: "",
start: "2012-10-18T15:57:41.511Z",
end: "2012-10-18T19:22:31.875Z",
sessionType: {
id: "1",
name: "Life Style",
}
}
When retrieving a Session Object from the server, I would like to have a SessionType Backbone.Model in order to add some business logic around this object.
So far I'm only able to retrieve an Object Session with a dummy SessionType, I can't add any logic on it because It doesn't belong to any Backbone model.
You can try this:
window.SessionType = Backbone.Model.extend({
initialize:function () {
},
});
Then in your session model, have a method:
window.Session = Backbone.Model.extend({
initialize:function () {
},
getSessionType () {
return new SessionType(this.get('sessionType'));
}
});
Now you can call the getSessionType() method which returns a model that can have your logic.
#Amulya is 100% correct. However, if you want the Session Model without having to call getSessionType(), I would look at using the the built in parse method and creating your model from there.
If your Session model is related to your model, I would look at using Backbone Relational. Since Backbone does not handle relationships, the plugin listed above does a fine job in filling the gap without too much manual labour.
Related
Here's an example that uses Backbone with React.
He defines a Model: var _todos = new Backbone.Model();
And then adds two functions to it:
var TodoStore = _.extend(_todos, {
areAllComplete: function() {
return _.every(_todos.keys(), function(id){
return _todos.get(id).complete;
});
},
getAll: function() {
return _todos.toJSON();
}
});
What I don't understand is why areAllComplete is being applied to a Model instead of to a Collection.
Shouldn't this be a function in a Collection that will get all of its models and check that complete attribute.
Similarly, I would expect getAll to belong to a Collection - get all of its models.
This example seems to replace Collection with Model.
Maybe I don't totally understand how models are used.
That example is using Backbone.Model in a fairly wierd way in my opinion.
This is where it's adding new todos to the store:
var id = Date.now();
_todos.set(id, {
id: id,
complete: false,
text: text
});
}
What it's basically doing is setting every todo-item as an attribute of the Model, using the id as the attribute name. It ends up with _todos.attributes looking something like below
{
"1436600629317": {
"id": 1436600629317,
"complete": false,
"text": "foo"
},
"1436600629706": {
"id": 1436600629706,
"complete": false,
"text": "bar"
}
}
That's the same output you get from _todos.toJSON(). I've no idea why they decided to implement it like that, if they were to try using Backbone.Sync they'd end up with a server API that's not exactly RESTful. It seems strange to use Backbone without leveraging any of the things Backbone provides. There's a reference to the change event here but I don't see it being used anywhere. You could easily reimplement that store using any regular JS object.
The only thing that example seem to be actually using from Backbone is Backbone.Events in the dispatcher. You're totally right that using a Collection would make way more sense because then you could actually make it talk to a REST based server API. That example seems to only use Backbone for the sake of using Backbone.
Here is what I am trying to understand.
Often times I find myself writing backbone like this:
var CallModel = Backbone.Model.extend({
});
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json'
});
It is a very basic example but as you can see, there is nothing really in the model all the data is coming into the Collection via an external url call to a json file that is build from a database.
So whats the purpose of the model? I am sure that I am probably not using backbone.js to its fullest extent which is why I am here asking you guys.
First of all, "there is nothing really in the model all the data is coming into the Collection via an external url call" - this is not true.
Let's assume you've the following:
//Model
var CallModel = Backbone.Model.extend({
defaults: {
cost:0,
duration:0
}
});
(without custom attributes or methods, there is no point in extending the original Backbone.Model)
//Collection
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json'
});
And the json data returned from service, probably something like:
//Response
{
callSummary: {
missed: 2,
received: 3,
totalCalls:5
totalDuration: 20
}
calls: [{
id:001,
caller:"Mr.A",
callee:"Mr.B",
cost:1,
duration:5
},{
id:002,
caller:"Mr.X",
callee:"Mrs.Y",
cost:1,
duration:7
},{
id:003,
caller:"Mr.A",
callee:"Mrs.B",
cost:1,
duration:8
}],
//and more additional information from your db
}
Now you populate your collection with data by calling it's fetch method:
CallsCollection.fetch();
Your collection should look something like:
{
models: [{
attributes: {
callSummary: {},
calls: [{},{},{}],
...
},
...
}],
length:1,
url: "url/to/external/json",
...
}
The data will be added to a model's attribute hash. If you don't specify a particular model, as Bart mentioned in his answer, backbone will populate the collection with a Backbone.Model instance: Which is still not much useful - Wew... A collection with single model having entire response data inside it's attributes as it is...
At this point, you're wondering why did I even bother creating a model, and then a collection..?
The problem here is Collections are derived from Arrays, while Models are derived from Objects. In this case, our root data structure is an Object (not an Array), so our collection tried to parse the returned data directly into a single model.
What we really want is for our collection to populate its models from the "calls" property of the service response. To address this, we simply add a parse method onto our collection:
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json',
parse: function(response){
/*save the rest of data to corresponding attributes here*/
return response.calls; // this will be used to populate models array
}
});
Now your collection will be something like the following:
{
models: [{
...
attributes: {
...
id:001,
caller:"Mr.A",
callee:"Mr.B",
cost:1,
duration:5
}
},{
...
attributes: {
...
id:002,
caller:"Mr.X",
callee:"Mrs.Y",
cost:1,
duration:7
}
},{
...
attributes: {
...
id:003,
caller:"Mr.A",
callee:"Mrs.B",
cost:1,
duration:8
}
}],
length:3,
url: "url/to/external/json",
...
}
This - is what we want! : Now it is very easy to handle the data: You can make use of the add, remove, find, reset and handful of other collection methods effectively.
You can pass this models array into your templating library of choice, probably with two way bindings: When the respective view for one of the call model changes, the particular model will be updated, events will propagate from your models to the collection, and the particular model will be passed into the handler functions.
You can now call fetch, save, destroy, clear and a lot of other methods with ease on single unit's of data (each model), rather than hurdle with the entire data saved in a single model - which is pretty much useless, you've to iterate through the response data manually and perform CRUD and similar operations by your own, and in most cases: re-render the entire collection view. which is very, very bad and totally unmaintainable.
To conclude: If your data source doesn't return an array of objects, or you don't parse the response and return an array of objects from which n number of models are to be populated - Then defining a collection is pretty much useless.
Hopefully, now you get the idea.
Very helpful source of info:
Backbone, The Primer: Models and Collections
Developing Backbone.js Applications
backbonejs.org
You don't need to specify a model. A Backbone collection will default to using Backbone.Model if you don't specify this option. The following would work equally well if you don't need the models of the collection to be of a particular instance.
var CallsCollection = Backbone.Collection.extend({
url: 'url/to/external/json'
});
Reference
EDIT
In essence, specifying the model option within a collection is just a way to ensure that objects added to this collection will be instances of that particular model class. If the models being added to your collection don't have any custom behaviour outside of what is available to Backbone.Model, you don't need to create and specify a model as Backbone collections will default to using an instance of Backbone.Model as I have already mentioned. If, however, you wanted to ensure that models added to a particular collection were of a particular type and shared customized behaviour (e.g. validations, defaults, etc.), you would create your own model class by extending Backbone.Model and specifying this in the collection. I hope this clears things up for you.
Sounds Weird but this is the way.
Every collection in backbone, must represent a model, so basically a collections is a list of models.
Even if your model has no data, you need to indicate it when you create a Collection.
This is how backbone works for collections.
I have an API that returns one resource at a time.
As an example, say I have two related resources:
Topics
Posts
Topics hasMany Posts, and Posts belongsTo a Topic.
According to the documentation (and everything I've tried and have been reading about), Ember-Data expects the JSON out of the API to be similar to the following, where both the data for Topic and the data for Posts are provided simultaneously.
"topic": {
"id": ...,
"title": ...,
"author": ...
},
"posts": [{
"id": ...,
"commenter": ...,
"text": ...
}, {
"id": ...,
"commenter": ...,
"text": ...
}]
As I mentioned at the start of the post, my API does not do this. It would return the Topic data in one call, and the Posts data for that Topic in a second call. Changing my API to work exactly how Ember wants it is not an option.
My question is, how can I extend/override the RESTAdapter and RESTSerializer in Ember-Data to support loading data from multiple API calls? Is this even possible?
I tried following this example, but it looks like the data here is already loaded (it tries to sideload data that is already known to the system): http://mozmonkey.com/2013/12/loading-json-with-embedded-records-into-ember-data-1-0-0-beta/
I also tried to override the extractFindAll function of the ApplicationSerializer, but can't figure out how to get the rest (no pun intended) of the application to wait for all of the relationships to load. I iterate through all of the relationships on the model, and load their data, but I don't see a way to shuttle that data back into the original payload:
extractFindAll: function (store, type, payload, id, requestType) {
var promises = [];
type.eachRelationship(function (key, relationship) {
promise.push(Ember.RSVP.Promise(function (resolve, reject) {
this.store.find(key, payloadEntry[key]);
}));
});
// can't figure out how to get the relationship payloads
// back to the original payload
Ember.RSVP.addSettled(promises).then(function () {
return this._super(store, type, payload, id, requestType);
});
}
Any help is appreciated.
That exactly describes using the async property.
Models
App.Foo = DS.Model.extend({
bars: DS.hasMany('bar', {async: true});
})
App.Bar = DS.Model.extend({
baz: DS.attr()
})
Adapters
App.FooAdapter = DS.RESTAdapter.extend({
//anything custom
});
App.BarAdapter = DS.RESTAdapter.extend({
//anything custom
});
If you don't do anything custom, you should just define App.ApplicationAdapter = DS.RESTAdapter; to be a site wide adapter.
Serializer
App.FooSerializer = DS.RESTSerializer.extend({
//anything custom
});
App.BarSerializer = DS.RESTSerializer.extend({
//anything custom
});
If you don't do anything custom, you don't need to define the rest serializers, they will use their default serializer associated with whichever adapter they're using.
Example
Important to note, I was lazy and my mock response for bars always returns the same thing regardless of any call to /bar*
http://emberjs.jsbin.com/OxIDiVU/481/edit
I saw in one example that you can encode a collection returned from a controller and use it in your script files as a normal array of Json objects.
Views/Shared/Index.cshtml
<script>
var photos = #Html.Raw(Json.Encode(Model))
</script>
This was done in the cshtml view where the controller returns data. However, it seemed that the 'photos' was visible in other script files and could be used to populate backbone models with that data.
I'm interested in how this works, what exactly is going on under the hood. When we Encode in Json format models returned from the controller, is that variable cached and lives through the life of the application, and is somehow a global variable containing that data? Thanks
The code in Scripts/app.js file:
var Photo = Backbone.Model.extend({ });
var firstPhoto= new Photo(photos[0]); // first photo model of the encoded collection
Can i do this? As in, serialize model into Json in my razor view, and consume the model in different .js files in my application ?
Can i do this? As in, serialize model into Json in my razor view, and
consume the model in different .js files in my application ?
Yes, you can consume the model throughout your application provided it is in scope. There's not much going on under the hood bar your C# View Model being serialized to a JSON object.
...is that variable cached and lives through the life of the
application, and is somehow a global variable containing that data?
There's nothing special going on, it behaves as normal - if you create your variable on the window or document then it will be available to you everywhere. If you create a local variable it will be available to you only within that scope.
The convention would be to bootstrap your application with the information you require and store that information in your application namespace.
So, in your case, you'd have something like:
window.App = {
Models: {},
Collections: {},
Views: {},
Routers: {},
Resources: {}
};
// App.Resources.Photos = #Html.Raw(Json.Encode(Model));
// So let's say your ViewModel contains an array of four Photo objects
// then your bootstrap object will look something like
App.Resources.Photos = [
{ Id: 1, Name: "Photo 1", Source: "mysite/photo1.png" },
{ Id: 2, Name: "Photo 2", Source: "mysite/photo2.png" },
{ Id: 3, Name: "Photo 3", Source: "mysite/photo3.png" },
{ Id: 4, Name: "Photo 4", Source: "mysite/photo4.png" }];
(function(){
App.Models.Photo = Backbone.Model.extend({
// whatever you want in here
});
App.Collections.Photo = Backbone.Collection.extend({
model: App.Models.Photo,
url: '/api/photos'
});
App.PhotoCollection = new App.Collections.Photo(App.Resources.Photos);
})();
You now have a Backbone collection that is a direct representation of your C# View Model. There's no need to refer back to the bootstrap object any longer. Generally, once you have cast your bootstrap object to a collection or model you don't need to refer back to it, hence the term bootstrap - it's there purely for you to create your Backbone data structure.
You can read more about this here and here.
So I'm using Backbone for my web application and I'm having trouble figuring out how to work with nested models. I have a model based on a java class whos JSON would look something like this:
"AdminSettings":
{
"defaults": {
"deletable":true,
"transferable":false;
"issueLimit":1
}
"displaySettings": {
"tabs":2,
"amounts:[10,20]
}
}
Currently, I have an endpoint set up and a backbone model for the AdminSettings object. I was wondering if there was a specific way to get all the backbone benefits for the objects inside AdminSettings. For example, currently I have to use:
adminSettings.get("defaultValues").shareable
But I want to use:
adminSettings.get("defaultValues").get("shareable")
This isn't the only benefit I'm trying to obtain, just an example.
So yeah, what would be a good way to go about this. I was thinking making a backbone model for each one of the nested objects and setting up endpoints for each one of those, but I'm not completely sure. Anyways, thanks for looking.
You could write your own custom backbone parser:
var DisplaySettingsModel = Backbone.Model.extend({});
var DefaultValuesModel = Backbone.Model.extend({});
var AdminSettingsModel = Backbone.Model.extend({
model: {
defaultValues: DefaultValuesModel
displaySettings: DisplaySettingsModel
},
parse: function(response){
response["defaultValues"] = new DefaultValuesModel(response["defaultValues"], {parse:true});
response["displaySettings"] = new DisplaySettingsModel(response["displaySettings"], {parse:true});
return response;
}
});