I would like to know how exactly a serialisation into JSON of a Backbone collection which contains attributes and models could be performed.
So per example if I have a collection like this:
var myCollection = Backbone.Collection.extend({
initialize: function (attr, options) {
this.property = options.property;
}
});
When trying to stringify with the JSON.stringify(myCollection) function, the stringify will call the object toJSON method defined in the Backbone Collection Object. Backbone collection defines this method as follows:
toJSON : function() {
return this.map(function(model){ return model.toJSON(); });
}
This means that only the models included in the collection will be included in the result JSON object but not the attributes of the collection I previously defined.
Do you know who this could be achieved?
Thanks.
Edit: Maybe not clear enough in my original question, I know I could override the toJSON method my questions is how exactly do it to achieve what I need in a general matter (not only for this specific attribute but imagine I have another collections which contains other properties, and all of them inherit from a baseCollection object. More over I would like to know how to restore that JSON object back to the backbone original state (not sure if by creating a new collection passing the JSON object in the parameters of the constructor would do the trick with this attributes)
You could override the "toJSON" method in your MyCollection class and return the string that you want.
You could override the toJSON method like #trunal-bhanse said, but if your goal is to sync that data with your server then you should probably find a better way of representing that data. Backbone Collections by default return an array when you call toJSON on an instance of one. That array then gets converted into a string using JSON.stringify before being sent to the server. However, the JSON.stringify method will not include any properties you've set on the array:
var test = ["foo", "bar", "baz"];
test.Hello = "World!";
console.log(test); // ["foo", "bar", "baz", Hello: "World!"]
JSON.stringify(test); // '["foo", "bar", "baz"]'
Related
I am probably not the first one doing this, but I can't find or figure out how to do it.
In my javascript I have an object with 5 properties, which are all object withg data properties and functions.
So it looks like this
function Maindata() {
var obj = this;
this.id = null;
this.profile= new Profile();
this.company= new Company();
//and more
this.getid = function(){
return obj.id;
};
}
and the profile and company object also has data and functions.
Now I get an object from the server, which is the same object, but only the data, and not the functions.
Now I want to project all of the properties of the retrieved object from the server to my JavaScript object.
I've read to use jQuery Extend, but that doesn't work for me, because it only preserves the functions on the root (Maindata) object, but not on the sub-objects.
How can I load all the data from the subobjects too?
Do I have to $extend the subobjects one by one, like this:
$.extend(true, maindata.profile, result.profile);
$.extend(true, maindata.company, result.company);
or is there another solution?
First of all, in general - you want to put all functionality on the prototype and all data on the object itself:
function Maindata() {
var obj = this;
this.id = null;
this.profile= new Profile();
this.company= new Company();
//and more
}
Maindata.prototype.getid = function() {
return this.id;
};
The prototype is assigned to every object created with Maindata as a constructor and it is shared across all Maindata instances. So it is likely more efficient and easier to reason about. It's kind of like classes in other languages.
This means you can do:
// creates an object with the given prototype without running the constructor
var o = Object.create(Maindata.prototype);
$.extend(o, result); // copy data over
// Object.assign(o, result); // with ES2015 assign syntax.
If your object is nested - then you need to do this in a nested way for subproperties. You can't get types for free since they are not encoded in the JSON. You can implement a custom serializer that does this automatically for you - some libraries like Backbone do this for you but IMO they do a pretty bad job and doing this yourself is pretty straightforward.
I am pulling an object from backbone.js and when I stringify the object I see string literal
'[{"Name":"Testname","Address":"Testaddress","id":"444444444444444"}]'
However, when I assign the non-serialized object to a variable and try to access the 0th element, I get undefined. I would expect to get object
{"Name":"Testname","Address":"Testaddress","id":"444444444444444"}
Is JavaScript not treating
[{"Name":"Testname","Address":"Testaddress","id":"444444444444444"}]
as an indexed array of objects?
To access elements of Backbone.Collection by index, use the Collection#at method:
var first = collection.at(0);
Alternatively, you can use the Collection#first method, which is actually part of the underscore library, but is proxied to Backbone collections for syntactic sugar:
var first = collection.first();
The reason you're seeing the array representation in the serialized JSON is that by convention JSON.stringify looks for a method called toJSON on the object you give to it to stringify, and if one is found, the return value of that method will be used instead. The implementation of Collection#toJSON returns a clone of the collection's internal array of models, and thus the JSON output is an array.
Just tried
var arr = JSON.parse( '[{"Name":"Testname","Address":"Testaddress","id":"444444444444444"}]' );
and
console.log( arr[0] ); // => object
What you've described should work.
I'm working with several backbone collections and sometimes I need to access parts of them based on some criteria.
METHOD 1
As already stated in this question, using filter() on the collection itself returns an array of models and not another collection. This can work in simple cases, but it has the effect of losing collection's method concatenation as a plain array of models won't have all methods defined in the collection.
METHOD 2
The answer to that question suggested creating a new collection passing the array of models to the constructor. This works but has the side effect of calling the collection's constructor every time, so any event binding that might be defined there gets stacked on every time you filter the collection.
So what's the correct way to create a sub-collection based on some filter criteria?
Should I use method 1 and create more filtering methods instead on relying on method chaining?
Should I go with method 2 and avoid binding events in the collection's constructor?
Personally I would create more filtering methods on the collection, because it has the additional benefit of encapsulating logic inside the collection.
You could also try to reuse the existing collection. I was toying around with the idea, and arrived at something like this:
var Collection = Backbone.Collection.extend({
//Takes in n arrays. The first item of each array is the method you want
//to call and the rest are the arguments to that method.
//Sets the collection.models property to the value of each successive filter
//and returns the result of the last. Revers the collection.models to its original value.
chainFilters: function(/*args..*/) {
var models = this.models;
try {
filters = _.toArray(arguments);
_.each(filters, function(filter) {
this.models = filter[0].apply(this, _.rest(filter));
}, this);
} catch(err) {
this.models = models;
throw err;
}
var filtered = this.models;
this.models = models;
return filtered;
}
});
Usage:
var results = collection.chainFilters(
[ collection.filter, function(model) { return model.get('name') === 'foo'; } ],
[ collection.someMethod, 'someargument' ],
[ collection.someOtherMethod ]
);
Here's a working sample. It's a bit peculiar, I know.
It depends on the use case. If you want those models to update a view then you probably want a new collection as otherwise you don't get the nice reactive template updates. If you simply wanted the models to iterate through or manipulate data without worrying about the data updating then use the array + underscore.js.
Try it with the arrays and if you find yourself writing a lot of boiler plate code with features already in a collection but not in underscore.js, just start using a collection.
I send a model to a template. The model has a collection. In the template I echo some variables and functions:
console.log(comments);
console.log(_.size(comments));
console.log(comments instanceof App.Collections.Comments);
console.log(_.pluck(comments, 'created'));
_.each(comments, function(com) {
console.log(com);
});
The first three work, but the last two underscore functions don't. Pluck gives 3x undefined and each doesn't iterate.
Object { length=3, models=[3], _byId={...}, more...}
3
true
[undefined, undefined, undefined]
How do I get the underscore functions to work?
Backbone collections have some Underscore methods mixed in so you can use the Underscore methods directly on the collection instance:
console.log(comments.pluck('created'));
comments.each(function(com) { console.log(com) });
Demo: http://jsfiddle.net/ambiguous/3jRNX/
This one:
console.log(_.size(comments));
works fine for you because _.size looks like this:
_.size = function(obj) {
return _.toArray(obj).length;
};
and _.toArray calls the collection's toArray:
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
which unwraps the collection's data to give you the correct length. The above is taken from the 1.3.1 source, the current Github master version's _.size has a different implementation so your _.size call is likely to break during an upgrade.
You'll want to call pluck directly on the collection, as the Collection class supports it:
http://documentcloud.github.com/backbone/#Collection-pluck
So instead of:
_.pluck(comments, 'created')
You chould call:
comments.pluck('created');
I have an object like this:
var someObj = Class.create ({
initialize: function(objName){
this.objName = objName;
}
});
I can use
o = new someObj("objName");
to make an obj. I can use Object.toJSON(o) to change the o to become a JSON String,
but I want the JSON String convert back to someObj, so, I use eval() to pass the
JSON String to become an object, but the question is, it can become a JS Obj,
but the constructor of "o" is not someObj. How can I eval the JSON String by using
"someObj" as the constructor?
JSON strings cannot represent objects with member functions, so the only thing you will get out of a JSON string is raw data. Assuming the toJSON method results in a JSON string representing an object with all the non-function members of your class instance, you should be able to take the resulting object and attach the prototype to get all the functions back. For example, using jQuery's handy extend function:
var o = new someObj("objName");
var json = Object.toJSON(o);
var json_obj = eval(json);
$.extend(json_obj, someObj.prototype);
json_obj.someMethodDefinedOnsomeObj()
Depending on how the framework you are using to represent classes in JavaScript makes use of the prototypal object model, your milage may very with the above example. Also, using eval() creates a security hole, so if you do not trust where the JSON string is coming from, you should use a different de-serialization method. Just for full coverage, here is how I did it with raw prototypes:
function Animal(name){
this.name = name;
}
Animal.prototype.talk = function(){
console.log("My name is "+this.name);
}
var a = new Animal("Brendan Eich");
a.talk();
var json = '{name: "Tim Berners-Lee"}'
var b = eval(b);
$.extend(b, Animal.prototype);
b.talk();
In a firebug console this produces the output:
My name is Brendan Eich
My name is Tim Berners-Lee
See JSON revivers at http://json.org/js.html
var myObject = JSON.parse(myJSONtext, reviver);
The optional reviver parameter is a
function that will be called for every
key and value at every level of the
final result. Each value will be
replaced by the result of the reviver
function. This can be used to reform
generic objects into instances of
pseudoclasses, or to transform date
strings into Date objects.
you're using prototypejs right? i've always found that tricky and ended up just making my own initializer that read in an object that had been evaled or the json string itself.