Backbone collection toJSON returning collection object - javascript

I have a model, which has a backbone collection as one of its attributes.
If I output the model, I can see the collection and all looks ok.
If I output the result of the collection toJSON(), it apparently outputs the whole of the collection object as json.
So the following two lines:
console.log('about to sync', model);
console.log('files', model.attributes.files.toJSON());
Gives the following output:
As you can see, the collection is present and correct in the model, but the toJSON call returns all of the functions in the object as well as the models and so on, instead of "an array containing the attributes hash of each model in the collection"

Backbone doesn't handle sub models/collections by default, you have to plug in your desired behavior. In your case, you just have to override toJSON on your model to replace the collection by its array representation.
Something like
var M = Backbone.Model.extend({
toJSON: function() {
var json = Backbone.Model.prototype.toJSON.call(this);
json.files = this.get('files').toJSON();
return json;
}
});
And a demo http://jsfiddle.net/nikoshr/1jk8gLz4/
Or if you prefer a more general change, alter Backbone.Model.prototype.toJSON to include this behaviour for all models. For example
(function () {
var originalMethod = Backbone.Model.prototype.toJSON;
Backbone.Model.prototype.toJSON = function(options) {
var json = originalMethod.call(this, options);
_.each(json, function(value, key) {
if ((value instanceof Backbone.Collection) ||
(value instanceof Backbone.Model))
json[key] = value.toJSON();
});
return json;
};
})();
http://jsfiddle.net/nikoshr/1jk8gLz4/2/

Tom, You will need to loop through each element in the object to see the contents after you have done toJSON(). I have also faced same problem. See how to parse the JSON object. While printing there would be any issues.
Hope this helps!!
Thanks

override toJSON method.
var yourModel = Backbone.Model.extend({
toJSON: function(){
var base = Backbone.Model.prototype.toJSON.call(this);
for (key in base) {
item = base[key];
if (item.toJSON != null) {
base[key] = item.toJSON();
}
}
return base;
}
});

Related

How do you prevent Knockback.js creating view models for null relations?

If my backbone models have relationships (for example, created by backbone-relational), those relationships might be nullable, leading the foreign key fields to sometimes be null.
If I have several knockback view models, and I've specified factories so that when following relations I get the view models with the desired functionality for the model, when it encounters an attribute that is null, it goes ahead and creates a view model passing null as the model, which likely breaks most of the view model's functionality.
Example:
var ChildViewModel = kb.ViewModel.extend({
constructor: function (model, options) {
// this is the problem I'm trying to avoid - creating a view model with
// no model
if (!model) {
// just report the error somehow - the jsfiddle has the
// relevant HTML element
document.getElementById("error").innerHTML = "ChildModelView initialised without a model!";
}
kb.ViewModel.prototype.constructor.apply(this, arguments);
}
});
var ParentViewModel = kb.ViewModel.extend({
constructor: function (model, options) {
// specify factories here, because this way you can easily deal with
// reverse relationships, or complicated relationship trees when you
// have a large number of different types of view model.
kb.ViewModel.prototype.constructor.call(
this,
model,
{
factories: {relation1: ChildViewModel,
relation2: ChildViewModel},
options: options
}
);
}
});
// if we assume that relation2 is a nullable relationship, backbone-relational,
// for example, would give us a model that looks like this:
var model = new Backbone.Model({
id: 1,
relation1: new Backbone.Model({id: 2}), // this works fine
relation2: null // this causes a problem
});
var view_model = new ParentViewModel(model);
And the fiddle:
https://jsfiddle.net/vbw44vac/1/
I've just discovered what I think might be a reasonable solution.
Your factories don't have to be ViewModel "classes", but can be factory functions. So:
var nullable = function (view_model_class) {
var factory = function (object, options) {
if (object === null) return object;
return new view_model_class(object, options);
};
return factory;
};
And then when you're defining your factories:
kb.ViewModel.prototype.constructor.call(
this,
model,
{
factories: {relation1: nullable(ChildViewModel),
relation2: nullable(ChildViewModel)},
options: options
}
);

Knockout mapping - difference between objects and observables in class being mapped to

This is driving me absolutely insane, and I just cannot see what I have done wrong. Please help before I start bibbling and gnawing my colleagues leg off. He doesn't deserve that.
I have an object I am mapping, which has a property that can or cannot contain an object of that same type. That is the only level of nesting there is. It is very simple; it is complicated only by the fact that the object is calling a base class constructor to set some default behaviour.
This base class sets up all the fields that can appear in the model (it is a generated file) and then maps the datasource, if it has one.
The mapping of the nested field to the correct constructor works if the field is set up initially as an observable. It does not if it is set up as a plain object.
var NS = {};
var _itest = 0;
NS.FieldModelBase = function(data, mapping)
{
var _this = this;
this.Text = ko.observable();
// DOES NOT WORK
this.AlternateField = {};
// WORKS
//this.AlternateField = ko.observable();
ko.mapping.fromJS(data, mapping, _this);
};
// =====================================================================
NS.FieldModel = function(data, mapping, parent)
{
var _this = this;
window.console && console.log('CREATING FIELD', data);
var _mapping = $.extend({}, mapping, {
'include': [ 'Test' ],
'AlternateField': {
create:
function(o)
{
window.console && console.log('FOUND SUBFIELD', o.data);
return o.data ? new NS.FieldModel(o.data) : null;
}
}
});
this.Test = ko.observable(_itest++);
NS.FieldModelBase.call(_this, data, _mapping);
}
// =====================================================================
var model = new NS.FieldModel({
Text: "Main option",
AlternateField: {
Text: "Alternate option",
AlternateField: null
}
}, { include: [ 'Test' ] });
ko.applyBindings(model);
https://jsfiddle.net/whelkaholism/fkr0w98u/
So, when setup as an object, printing model out after running the code gives:
{"Test":0,"Text":"Main option","AlternateField":{"Text":"Alternate option","AlternateField":null}}
There is no Test property on the alternate field. If you check the console, what happens is that the mapping create is in fact called, but the o.data property is null.
Change to an observable, and the output is, as expected:
{"Test":0,"Text":"Main option","AlternateField":{"Test":1,"Text":"Alternate option","AlternateField":null}}
So, what is the mapping plugin doing here? It was my understanding that it would map everything in source data, regardless of the existence or type of any existing properties on the object?
EDIT: I have solved my immediate problem with this change:
NS.FieldModel = function(data, mapping, parent)
{
var _this = this;
var _mapping = {
copy: [ 'AlternateField' ]
};
NS.FieldModelBase.call(_this, data, _mapping);
this.AlternateField = data.AlternateField ? new NS.FieldModel(data.AlternateField, null, _this) : null;
}
This manually creates the correct object type for the alternate field after the mapping. The copy directive in the mapping is absolutely required, or the newly created object has no properties mapped.
I don't now why this is, so I'm still looking for the answer on why the mapping plugin works differently depending on the content of pre-existing variables, because I despise having code that I don't know exactly why it works!
The mapping plugin maps only values, not objects e.g.
"myString" => ko.observable("myString")
null => ko.observable(null)
{ myStringProperty: "myString" } => { myStringProperty: ko.observable("myString") }
{ myProperty: null } => { myPropery: ko.observable(null) }
{} => {}

Is there any way to convert Ember Object into plain javascript object?

I could not find any way to accomplish the task of such conversion as I could not find any means of getting Ember.js properties for the object. Ember.keys returns only the properties I set in create or with get and the properties declared in Ember.extend do not show up there. I use such properties to set up default values (e.g. [] for array properties)
Here is my dirty workaround
var newModel = JSON.parse(JSON.stringify(model));
I would do something similar to the person above, but I'd do it a little bit differently.
Mixin
App.NativeObject = Ember.Mixin.create({
toNative: function() {
var properties = [];
for (var key in this) {
if (jQuery.inArray(Ember.typeOf(object[key]), ['string', 'number', 'boolean']) !== -1) {
properties.push(key);
}
}
return this.getProperties(properties);
}
});
Object
Then you just need to implement the App.NativeObject mixin in your objects that you would like the toNative on:
var Object = Ember.Object.extend(App.NativeObject, {
name: 'Adam',
count: 4
});
We then have the toNative method on all the objects that implement our mixin.
Obligatory jsFiddle: http://jsfiddle.net/jumUx/
If your object is a subclass of ember-data model notice you can use the toJSON method otherwise you can use:
JSON.parse(JSON.stringify(emberObj))
To grab any values which support native json serialization (i.e. not functions/methods)
This worked for me:
myModel.toJSON({includeId: true})
I'm using Ember 3.
This is what I did and it works quite well. Note, this should be ready only, as any changes to an object or array in the copied object will affect the original object
App.BaseValidations = Ember.Object.create({
toObject: function() {
var destination = {}
for (var k in this) {
if (this.hasOwnProperty(k) && typeof(this[k]) !== 'function') {
destination[k] = this[k];
}
}
return destination;
}
})
something quite simple that worked properly enough for me is :
Ember.Object.reopen({
toJson: function() {
return JSON.parse(JSON.stringify(this));
}
});
at app loading time.
At the moment I solve it with the following snippet:
App.plainCopy = function (obj) {
if (Ember.isArray(obj)) {
return obj.map(App.plainCopy);
} else if (typeof(obj) === "object") {
if (App.Plainable.detect(obj)) {
return obj.plainCopy();
} else {
throw new Error(Ember.String.fmt("%# is not Plainable", [obj]));
}
} else {
return obj;
}
}
App.Plainable = Ember.Mixin.create({
plainCopy: function() {
var props = Ember.keys(this);
var proto = this.constructor.prototype;
for(p in proto) {
if (proto.hasOwnProperty(p) && typeof(this[p])!=="function") {
props.push(p);
}
}
var copy = {};
props.forEach(function(p) {
copy[p] = App.plainCopy(this.get(p));
}, this);
return copy;
}
});
It does not go up the class hierarchy and does not look into mixins (as I use for data objects which are quite simple form that point of view)
With modern (3.17) ember, I've used myEmberObject.getProperties('id', 'name', 'foo', 'bar')
It produces a plain object.
Another possible solution that may suit your needs while not being fully recursive for nested Ember objects:
// where myEmberObject is.. an ember object
var plainJavaScriptObject = myEmberObject.toJSON();
This will only include actual properties that you've defined and no Ember internals. Again, the drawback here is that any nested Ember objects will not, themselves, be converted but will appear as Strings in style of "".

set attribute of all models in backbone collection

I understand that using pluck method we can get an array of attributes of each model inside a backbone collection
var idsInCollection = collection.pluck('id'); // outputs ["id1","id2"...]
I want to if there is a method that sets up an attribute to each model in the collection,
var urlArray = ["https://url1", "https://url1" ...];
collection.WHAT_IS_THIS_METHOD({"urls": urlArray});
There's not exactly a pre-existing method, but invoke let's you do something similar in a single line:
collection.invoke('set', {"urls": urlArray});
If you wanted to make a re-usable set method on all of your collections, you could do the following:
var YourCollection = Backbone.Collection.extend({
set: function(attributes) {
this.invoke('set', attributes);
// NOTE: This would need to get a little more complex to support the
// set(key, value) syntax
}
});
* EDIT *
Backbone has since added its own set method, and if you overwrite it you'll completely break your Collection. Therefore the above example should really be renamed to setModelAttributes, or anything else which isn't set.
I don’t there is a method for it, but you can try:
collection.forEach(function(model, index) {
model.set(url, urlArray[index]);
});
Expanding on David's answer, you can easily put this functionality into a custom method on the collection. Here's my way of doing it using coffeescript:
class Collection extends Backbone.Collection
setAll: () ->
_args = arguments
#models.forEach (model) -> model.set _args...
class SomeCollection extends Collection
url: '/some-endpoint.json'
myLovelyCollection = new SomeCollection()
myLovelyCollection.fetch
success: (collection, response) ->
collection.setAll someVar: true
collection.setAll anotherVar, 'test'
If you wanted to do it in vanilla JS it's exactly the same but not harnessing the power of classes or splats. So more like:
var Collection = Backbone.Collection.extend({
setAll: function () {
var _args = arguments;
this.models.forEach(function (model) {
model.set.apply(model, _args);
});
}
});
Just thought I'd post my slightly updated method based on machineghost's version. This uses lodash invokeMap method instead of underscore's invoke. It supports the same optional syntax as the standard model.set method ... e.g. ('prop', 'val') or ({prop: 'val', prop: 'val'}) as well accepting and passing an options object.
var YourCollection = Backbone.Collection.extend({
setModels: function(key, val, options) {
var attrs;
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
if (attrs) {
_.invokeMap(this, 'set', attrs, options);
}
return this;
}
});
If you are using invoke the syntax according to the underscore site should be
_.invoke(list, methodName, *arguments) http://underscorejs.org/#invoke
So the above function mentioned by machineghost should be
collection.invoke({'url': someURL},'set');
Hope that helps :)

Is there some way to add meta-data to JavaScript objects?

I would like to add key-value pairs of metadata to arbitrary JavaScript objects. This metadata should not affect code that is not aware of the metadata, that means for example
JSON.stringify(obj) === JSON.stringify(obj.WithMetaData('key', 'value'))
MetaData aware code should be able to retrieve the data by key, i.e.
obj.WithMetaData('key', 'value').GetMetaData('key') === 'value'
Is there any way to do it - in node.js? If so, does it work with builtin types such as String and even Number? (Edit Thinking about it, I don't care about real primitives like numbers, but having that for string instances would be nice).
Some Background: What I'm trying to do is cache values that are derived from an object with the object itself, so that
to meta data unaware code, the meta data enriched object will look the same as the original object w/o meta
code that needs the derived values can get it out of the meta-data if already cached
the cache will get garbage collected alongside the object
Another way would be to store a hash table with the caches somewhere, but you'd never know when the object gets garbage collected. Every object instance would have to be taken care of manually, so that the caches don't leak.
(btw clojure has this feature: http://clojure.org/metadata)
You can use ECMA5's new object properties API to store properties on objects that will not show up in enumeration but are nonetheless retrievable.
var myObj = {};
myObj.real_property = 'hello';
Object.defineProperty(myObj, 'meta_property', {value: 'some meta value'});
for (var i in myObj)
alert(i+' = '+myObj[i]); //only one property - #real_property
alert(myObj.meta_property); //"some meta value"
More information here: link
However you're not going to be able to do this on primitive types such as strings or numbers, only on complex types.
[EDIT]
Another approach might be to utilise a data type's prototype to store meta. (Warning, hack ahead). So for strings:
String.prototype.meta = {};
String.prototype.addMeta = function(name, val) { this.meta[name] = val; }
String.prototype.getMeta = function(name) { return this.meta[name]; };
var str = 'some string value';
str.addMeta('meta', 'val');
alert(str.getMeta('meta'));
However this is clearly not ideal. For one thing, if the string was collected or aliased (since simple data types are copied by value, not reference) you would lose this meta. Only the first approach has any mileage in a real-world environment, to be honest.
ES6 spec introduces Map and WeakMap. You can enable these in node by running node --harmony and by enabling the experimental javascript flag in Chrome, (it's also in Firefox by default). Maps and WeakMaps allow objects to be used as keys which can be be used to store metadata about objects that isn't visible to anyone without access to the specific map/weakmap. This is a pattern I now use a lot:
function createStorage(creator){
creator = creator || Object.create.bind(null, null, {});
var map = new Map;
return function storage(o, v){
if (1 in arguments) {
map.set(o, v);
} else {
v = map.get(o);
if (v == null) {
v = creator(o);
map.set(o, v);
}
}
return v;
};
}
Use is simple and powerful:
var _ = createStorage();
_(someObject).meta= 'secret';
_(5).meta = [5];
var five = new Number(5);
_(five).meta = 'five';
console.log(_(someObject).name);
console.log(_(5).meta);
console.log(_(five).meta);
It also facilitates some interesting uses for separating implementation from interface:
var _ = createStorage(function(o){ return new Backing(o) });
function Backing(o){
this.facade = o;
}
Backing.prototype.doesStuff = function(){
return 'real value';
}
function Facade(){
_(this);
}
Facade.prototype.doSomething = function doSomething(){
return _(this).doesStuff();
}
There is no "comment" system in JSON. The best you can hope for is to add a property with an unlikely name, and add that key contaning the metadata. You can then read the metadata back out if you know it's metadata, but other setups will just see it as another property. And if someone uses for..in...
You could just add the Metadata as a "private" variable!?
var Obj = function (meta) {
var meta = meta;
this.getMetaData = function (key) {
//do something with the meta object
return meta;
};
};
var ins_ob = new Obj({meta:'meta'});
var ins_ob2 = new Obj();
if(JSON.stringify(ins_ob) === JSON.stringify(ins_ob2)) {
console.log('hoorai');
};
If you want object-level metadata, you could create a class that extends Object. Getters and setters are not enumerable and, obviously, neither are private fields.
class MetadataObject extends Object {
#metadata = undefined;
get metadata() { return this.#metadata; }
set metadata(value) { this.#metadata; }
}
var obj = new MetadataObject();
obj.a = 1;
obj.b = 2;
obj.metadata = { test: 123 };
console.log(obj); // { a: 1, b: 2 }
console.log(obj.metadata); // { test: 123 }
console.log(JSON.stringify(obj)); // '{"a":1,"b":2}'
You can even simplify the implementation using a Map. Without a setter on metadata, you have to use Map methods to modify it.
class MetadataObject extends Object {
#metadata = new Map();
get metadata() { return this.#metadata; }
}
var obj = new MetadataObject();
obj.a = 1;
obj.b = 2;
obj.metadata.set('test', 123);
console.log(obj); // { a: 1, b: 2 }
console.log(obj.metadata.get('test')); // 123
console.log(JSON.stringify(obj)); // '{"a":1,"b":2}'
I ran into a situation where I needed property level metadata, and used the latter implementation.
obj.id = 1;
obj.metadata.set('id', 'metadata for the id property');

Categories

Resources