It seems that neither Backbone collection fetch nor parse functions call constructor for the retrieved models; am I right? So if one needs to initialize some special properties for them (in constructor or initializer), he must call it explicitly. E. g., in success callback of the fetch function. Or may be I miss something?
Example follows.
return t.collection.fetch({
success: function () {
for (var i = 0; i < t.collection.models.length; i++) {
t.collection.models[i] = new MyModel(t.collection.models[i]);
}
},
});
Here the t.collection is of type
class MyModelsCollection extends Backbone.Collection<MyModel> {
url = "/api/MyModels/";
}
The TypeScript has been used.
Solution. The answer made me to check the TypeScript syntax used here. I have found that it was incorrect. It must be like in this answer (except that the super() call should go first). So one should set the model property of the collection manually in the constructor.
May be it was obvious for everyone else.
If you just link your model class to model option of the collection, everything will work by itself.
var MyModel = Backbone.Model.extend({});
var MyCollection = Backbone.Colleciton.extend({
url: "/api/MyModels/",
model: MyModel
});
When instance of MyCollection will be fetched and there will be an array of models from backend, it will create needed amount of MyModel instances.
Related
I am very new in backbone js.
I am trying to filter some specific key and values in backbone js model extend here is the code below.
var items = ["open","close"];
var ReportModel = Backbone.Model.extend({
url: function() {
return tab+".json";
}
});
where tabe is dynamic json file name.In my json file many key value pair are there but I want to load only those key which is mentioned in items list.
I saw some where using parse function but that a;so did not work out.Please do let me know how to filter the specific keys form json using the backbone.
I also tried creating a dict from json and pass it to model like.
var ReportModel = Backbone.Model.extend({
"open":{.......}
});
but there I am getting issue.
throw new Error('A "url" property or function must be specified');
Please help me out with this.
You are missing some steps to be succesfull on your task.
First a note about the error: Backbone expects a string on the url property while you're passing a function. If you want to use a function to return your url dinamically use urlRoot.
Now onto the real coding:
since you talk about a json file that has multiple key value, maybe you should declare your model as a key-value object, and then create a Backbone.Collection that will wrap your models.
A Backbone.Collection expose a lot of utilities that can help us modeling the results, in this case by using the where() function of our collection you will be able to filter the data after you have retrieved from the remote file.
Alternatively to filter your collection if you need more control over the function you can always call the undescore function filter() .
Please refer to the official documentation of underscore and backbone, as you will find a lot of functions that can help you and most of them have an example that shows how to use them.
Now that we have everything lets create our Backbone.Collection that will wrap your already defined model:
var ReportCollection = Backbone.Collection.extend({
model: ReportModel,
urlRoot: function(){
return 'yoururl.json';
}
});
now if you want to filter the result you can simply fetch the collection and perform a filter on it:
var myReports = new ReportCollection();
//call the fetch method to retrieve the information from remote
myReports.fetch({success: function(){
//the collection has been fetched correctly, call the native where function with the key to be used as a filter.
var filteredElements = myReports.where({my_filter_key : my_filter_value});
});
in your filteredElements you will have an array of object made up of all the model that matched the key/value passed to the where function.
If you need a new Collection from that you just need to pass the result as argument: var filteredCollection = new ReportCollection(filteredElements);
You can use _.pick() in the parse method as shown below:
var items = ["open", "close"];
var ReportModel = Backbone.Model.extend({
url: function() {
return tab + ".json";
},
parse: function(response) {
return _.pick(response, items);
}
});
I have some code that looks like:
var instance = new ModelA(element);
if(instance.isValid()){
CollectionA.add(instance);
}
Is there a better way to write this? Would prefer that either initializing ModelA or adding to CollectionA would fail or throw.
You have to override the constructor.
See here for more info.
Backbone has this baked right in. There is a validate method available in your model code which will get called before any save (You can also do it for set by passing {validate:true}
Here's a snippet from the backbone docs:
var Chapter = Backbone.Model.extend({
validate: function(attrs, options) {
if (attrs.end < attrs.start) {
return "can't end before it starts";
}
}
});
If validate returns anything, then the Backbone SAVE won't happen, but if it goes through your validate function cleanly without any returns, then it will go ahead with the save.
I have a constructor for a model (similar to Backbone model) which uses an instance of a store (e.g. MongoDB / Redis) which is passed to the model factory. Now inside the constructor for the model, I do
this.store = options.store;
Store is now available as this.store once I construct an instance with var model = new Model().
Then I faced a situation when I my Model has "public" methods, e.g. Model.find() which will work without instantiating the model. But now because it's not been instantiated, functions inside Model can not access store by this.store.
What I did, is I started also adding store to the constructor itself Model.store = options.store which fixes the problem. But now store is exposed to anything that uses Model constructor and this is not desirable.
I am guessing I am doing something wrong. So I would appreciate some help.
If I get what you're saying correctly, then I think what you want is to allow for a "static" find() method on Model without exposing the store variable. Javascript doesn't really have a formal private scope for classes, but you can get what you want using closures. Here's a fiddle:
http://jsfiddle.net/52FPy/
EDIT:
An updated fiddle to demonstrate various ways to expose/hide info using closures:
http://jsfiddle.net/52FPy/2/
Briefly:
var Model = function(){
var store = 'this is a store';
var Model = function(){
}
Model.find = function(){
return store;
}
return Model;
}()
This will "hide" the store variable in the way you want.
I have a simple Backbone model that looks like this:
(function () {
App.Company = Backbone.Model.extend({
defaults: {},
urlRoot: "/Contacts/Companies",
initialize: function () {
var contactPersons = this.get("ContactPersons") || [];
this.set("ContactPersons", new App.ContactPersonCollection(contactPersons));
}
});
})();
Whenever I save the model to the server, the ContactPersons collection is reset to an Array.
Is it really necessary for me to manually turn it into a collection, after a model is saved?
UPDATE: This works as intended -- See answer for better approach (IMHO)
(function () {
App.Company = Backbone.Model.extend({
defaults: {},
urlRoot: "/Contacts/Companies",
initialize: function () {
var contactPersons = this.get("ContactPersons") || [];
if (_.isArray(contactPersons)) {
this.set("ContactPersons", new App.ContactPersonCollection(contactPersons));
}
},
parse: function (response) {
if (response.ContactPersons && _.isArray(response.ContactPersons)) {
response.ContactPersons = new App.ContactPersonCollection(response.ContactPersons);
}
return response;
}
});
})();
When you send data back from the server, how are you handling the response? For example if you just send back a [{},{},{}] I don't think Backbone automatically knows to treat that as a collection. Thus, it sets the ContactPersons attribute as what it gets, your vanilla array.
What you can do, is override your set function inside your model which will take the array of objects passed in and write to the collection as proper. See this example:
set: function(attributes, options) {
if (_.has(attributes, 'ContactPersons') && this.get("ContactPersons")) {
this.get('ContactPersons').reset(attributes.ContactPersons);
delete attributes.ContactPersons;
}
return Backbone.Model.prototype.set.call(this, attributes, options);
}
So basically as long as your server response is properly namespaced (response.ContactPersons) then after parsing it will pass your response to the set function. The collection data is treated specially as a collection. Here, I'm just reseting the collection that already exists with the new data. All your other model attributes should continue to be passed on to the original set().
UPDATE - Growing doubt about own answer
I haven't been able to get this question/answer out of my mind. It certainly works, but I'm becoming unconvinced that using a modified set() vs. just doing things in parse() is any better. If someone has some comments on the difference between using a modified set() vs. parse() with nested models, I'd really welcome the input.
I have the following in test.html:
<script>
var Foo = Backbone.Model.extend({
initialize: function(options) {
console.log('hello!');
}
});
var Bar = Backbone.Collection.extend({
model: Foo
});
var m = new Bar({});
</script>
As it turns out, when the variable m is initialized, the initialize function of Foo is called. Thus, in the Firebug console, I get 'hello!'. When I comment out the line:
model: Foo,
There is no 'hello!' in the console output. Thus, declaring a model for a collection calls that model's initialize function. I think this behavior is a bit silly. I haven't read through backbones code, but is there a reason for this?
Well, there's nothing wrong with the behaviour of the code.
When you pass the model in the collection definition, you specify that every model in that collection will be of type Foo.
When you initialize the collection new Bar({}), you pass a model to the collection (although, as #alexanderb stated, I think that the collections expects an array as a first argument) and it initializes it, thus outputting 'hello!'.
For example, if you do not pass any models to the collection constructor :
new Bar();// no console output
there will be no console output, and on the other hand, if you would pass an array of objects, then the collection would initialize all of the provided models :
new Bar([{},[},{}]);// will output 'hello!' 3 times
I believe the constuctor of collection is expecting the array of models. So, what you should do is:
var collection = new Bar( [ {} ] );
There, the initialize method of model should be called.
After a bit of investigation here is what i found out, the Backbone.Collection function is as follows:
var Collection = Backbone.Collection = function(models, options) {
options || (options = {});
if (options.model) this.model = options.model;
if (options.comparator !== void 0) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
if (models) this.reset(models, {silent: true, parse: options.parse});
};
So if you create an initialize method for your Collection like this
initialize: function() {
console.log('hello collection!');
}
You will notice that the hello collection is logged before the hello from model. So the model initialisation must come from the reset function after the initialize-call. rest won't be called unless you have models passed onto your collection, which you at a quick glance don't seem to be doing, but actually in
var m = new Bar({});
Backbone interprets the {} as a model and thus initializes it in the reset-function. But {} isn't a model you say? Well, Backbone isn't too picky about that, it just needs an array of hashes that could or could not contain model attributes. The reset-function eventually leads to the add-function and finally all roads go to Rome, or should i say the _prepareModel-function
_prepareModel: function(attrs, options) {
if (attrs instanceof Model) {
if (!attrs.collection) attrs.collection = this;
return attrs;
}
options || (options = {});
options.collection = this;
var model = new this.model(attrs, options);
if (!model._validate(model.attributes, options)) return false;
return model;
}
What happens here is that the Collection checks whether or not it has been passed a model or a hash of attributes and in the hash-of-attributes case it just creates a new model based on its defined model and passes that hash along.
Hope this not only solves the problem, but sheds some additional light on what happened there. And of course I warmly promote for everyone to read up on the backbone source code, the baddest OG of documentation.