Backbone relational model saving error - javascript

I get the error, when I try to save the model with .save()
Converting circular structure to JSON
The funny thing is that modelInstance.toJSON() works just fine.
The error is thrown at backbone.js line 1148
which is:
params.data = JSON.stringify(options.attrs || model.toJSON(options));
Here is how I've setup of the model:
var Clip = Backbone.RelationalModel.extend({
idAttribute: "mediaItemId",
defaults: {
node: {}
}
});
var clipCollection = Backbone.Collection.extend({
model: Clip
});
var mainModel = Backbone.RelationalModel.extend({
url: '/api/v0/videostate',
relations: [
{
type: Backbone.HasMany
,key: 'videoCollection'
,relatedModel: Clip
,collectionType: clipCollection
,includeInJSON: Clip.idAttribute
,reverseRelation: {
key: 'parent',
includeInJSON: Clip.idAttribute
}
}
],
});
var modelInstance = new mainModel()
modelInstance.fetch();
The JSON that's loaded into the model:

Change includeInJSON: Clip.idAttribute in reverse relation to includeInJSON: Clip.prototype.idAttribute
Something like this
{
type: Backbone.HasMany
,key: 'videoCollection'
,relatedModel: Clip
,collectionType: clipCollection
,includeInJSON: Clip.prototype.idAttribute
,reverseRelation: {
key: 'parent',
includeInJSON: Clip.prototype.idAttribute
}
}

Created a JSFiddle with above code , http://jsfiddle.net/ravikumaranantha/PuLxQ/6/, it doesn't throw any error.
var Clip = Backbone.RelationalModel.extend({
idAttribute: "mediaItemId",
defaults: {
node: {} //could be problem here
}
});
I just sense problem could be (not sure) with having an object in defaults map, you should avoid using objects/arrays in defaults, they will get shared across all instances. If you can post response from fetch call, that should help us debug it further.

Related

Update model with data retrieved from backbone.sync call

I have a backbone model similar to this.
User = Backbone.Model.extend({
defaults: {
id: null,
name: '',
groups: []
},
addGroup: function(group) {
return this.sync(
'update',
this,
{url: this.url() + 'add_group', data: 'group=' + group}
);
}
}
UserCollection = Backbone.Collection.extends({
model: User,
url: '/api/users'
});
The purpose behind this call is that adding the permission triggers all sorts of backend checks and other changes. The actual code, which I'm trying to not to expose here, demonstrates the need better.
The /api/users/add_group endpoint returns a new representation of the Users model, which I wish to have applied to the model with all of the appropriate events triggered. The best work-around I could find is this.
User = Backbone.Model.extend({
defaults: {
id: null,
name: '',
groups: []
},
addGroup: function(group) {
model = this;
return this.sync(
'update',
this,
{
url: this.url() + 'add_group',
data: 'group=' + group,
success: function() {model.fetch();}
}
);
}
}
However, it feels like there is probably a better solution where I can call arbitrary endpoints and have the model updated with the returned data.

Giving a single reference to multiple Backbone.Models

I have a Backbone.Model which looks something like:
var FooModel = Backbone.Model.extend({
defaults: {
details: '',
operatingSystem: ''
};
});
There are many instances of FooModel which are stored in a collection:
var FooCollection = Backbone.Collection.extend({
model: FooModel
});
FooModel's OperatingSystem is a property which only needs to be calculated once and is derived asynchronously. For example:
chrome.runtime.getPlatformInfo(function(platformInfo){
console.log("Operating System: ", platformInfo.os);
});
If I perform this logic at the FooModel level then I will need to perform the logic every time I instantiate a FooModel. So, I think that this operation should be performed at a higher level. However, it is bad practice to give properties to a Backbone.Collection.
As such, this leaves me thinking that I need a parent model:
var FooParentModel = Backbone.Model.extend({
defaults: {
platformInfo: '',
fooCollection: new FooCollection()
},
initialize: function() {
chrome.runtime.getPlatformInfo(function(platformInfo){
this.set('platformInfo', platformInfo);
}.bind(this));
},
// TODO: This will work incorrectly if ran before getPlatformInfo's callback
createFoo: function(){
this.get('fooCollection').create({
details: 'hello, world',
operatingSystem: this.get('platformDetails').os
});
}
});
This works and is semantically correct, but feels over-engineered. The extra layer of abstraction feels unwarranted.
Is this the appropriate way to go about giving a property to a model?
Although Backbone Collections may not have attributes, they may have properties (as well as any object) which you can use to store shared data.
var FooCollection = Backbone.Collection.extend({
model: FooModel
initialize: function() {
this.platformInfo = null; // shared data
chrome.runtime.getPlatformInfo(function(platformInfo){
this.platformInfo = platformInfo;
}.bind(this));
},
// wrapper to create a new model within the collection
createFoo: function(details) {
this.create({
details: details,
operatingSystem: this.platformInfo? this.platformInfo.os : ''
});
}});
});

ExtJS: Model's not working as intended

I don't understand whats wrong. Trying to learn from the Sencha doc's
app/model/Customer.js
Ext.define('myapp.model.Customer', {
extend: 'Ext.data.Model',
fields: ['id', 'name'],
proxy: {
type: 'rest',
url: 'data/customer'
}
});
app/controller/myController.js
Ext.define('myapp.controller.myController', {
extend: 'Ext.app.Controller',
models: ['Customer'],
...
onSomeEvent: function() {
var cust = Ext.create('Customer', {name: 'neo'});
cust.save();
}
});
I'm getting an Uncaught TypeError: object is not a function error, and my server is logging a GET /Customer.js?_dc=1395954443
It seems like error is getting thrown when you are creating model instance.
To create a model instance, you will need to use fully qualified model name i.e.
var cust = Ext.create('myapp.model.Customer')
Or you could this:
var cust = this.getCustomerModel().create()

Backbone JS complex model fetch

I have two backbone models, loaded from server:
var Model = Backbone.Model.extend({});
var SubModel = Backbone.Model.extend({});
var SubCollection = Backbone.Collection.extend({
model: SubModel
});
var m = new Model();
m.fetch({success: function(model)
{
model.submodels = new SubCollection();
model.submodels.url = "/sub/" + model.get("id");
model.submodels.fetch();
}});
So, the server has to send two separate responses. For example:
{ name: "Model1", id: 1 } // For Model fetch
and
[{ name: "Submodel1", id: 1 }, { name: "Submodel2", id: 2 }] // For Submodel collection fetch
Is there a way to fetch a Model instance with Submodel collection at once, like:
{
name: "Model1",
id: 1,
submodels: [{ name: "Submodel1", id: 2 }, { name: "Submodel1", id: 2 }]
}
To be able to do that is up to your back-end - it doesn't really have anything to do with Backbone.
Can you configure your back-end technology to return related models as nested resources?
If your back-end is Rails, for instance, and your models are related in ActiveRecord, one way of doing this is something like
respond_to do |format|
format.json { render :json => #model.to_json(:include => [:submodels])}
end
What back-end technology are you using?
Edit:
Sorry, misunderstood the gist of your question, once you've got your back-end returning the JSON in the proper format, yeah, there are things you need to do in Backbone to be able to handle it.
Backbone-Relational
One way to deal with it is to use Backbone-Relational, a plugin for handling related models.
You define related models through a 'relations' property:
SubModel = Backbone.RelationalModel.extend({});
SubCollection = Backbone.Collection.extend({
model: SubModel
});
Model = Backbone.RelationalModel.extend({
relations: [
{
type: 'HasMany',
key: 'submodels',
relatedModel: 'SubModel',
collectionType: 'SubCollection'
}
]
});
When your Model fetches the JSON, it will automatically create a SubCollection under the 'submodels' property and populate it with SubModels - one for each JSON object in the array.
jsfiddle for backbone-relational: http://jsfiddle.net/4Zx5X/12/
By Hand
You can do this by hand if you want as well. In involves overriding the parse function for your Model class (forgive me if my JS is not 100% correct - been doing CoffeeScript so much lately its hardwired in my brain)
var Model = Backbone.Model.extend({
parse: function(response) {
this.submodels = new SubCollection();
// Populate your submodels with the data from the response.
// Could also use .add() if you wanted events for each one.
this.submodels.reset(response.submodels);
// now that we've handled that data, delete it
delete response.submodels;
// return the rest of the data to be handled by Backbone normally.
return response;
}
});
parse() runs before initialize() and before the attributes hash is set up, so you can't access model.attributes, and model.set() fails, so we have to set the collection as a direct property of the model, and not as a "property" that you would access with get/set.
Depending on what you want to happen on "save()" you may have to override `toJSON' to get your serialized version of the model to look like what your API expects.
jsfiddle:
http://jsfiddle.net/QEdmB/44/

How to get data from extjs 4 store

Im stack with ext js 4 at the very beginning. Im trying to get the current user data when starting the application using store. But Im not getting any data from the store, even the store.count return 0.
I found many description how to create store, but not how to access the data in it. I managed to get the data using Ext ajax request, but i think would be better using store and i cant avoid them..
My model:
Ext.define('MyApp.model.User', {
extend: 'Ext.data.Model',
fields: [
'id',
'username',
'email'
]
});
My store looks like:
Ext.define('MyApp.store.User.CurrentUser', {
extend: 'Ext.data.Store',
requires: 'MyApp.model.User',
model: 'MyApp.model.User',
autoLoad: true,
proxy: {
type: 'ajax',
method: 'POST',
url: Routing.generate('admin_profile'),
reader: {
type: 'json',
root: 'user'
}
}
});
The returned json:
{
"success":true,
"user":[{
"id":1,
"username":"r00t",
"email":"root#root.root"
}]
}
And the application:
Ext.application({
name: 'MyApp',
appFolder: '/bundles/myadmin/js/app',
models: ['MyApp.model.User'],
stores: ['MyApp.store.User.CurrentUser'],
//autoCreateViewport: true,
launch: function() {
var currentUser=Ext.create('MyApp.store.User.CurrentUser',{});
/*
Ext.Ajax.request({
url : Routing.generate('admin_profile'),
method: 'POST',
success: function(resp) {
var options = Ext.decode(resp.responseText).user;
Ext.each(options, function(op) {
var user = Ext.create('MyApp.model.User',{id: op.id,username:op.username,email:op.email});
setUser(user);
}
)}
});
*/
currentUser.load();
alert(currentUser.count());
}
});
The problem itself isn't that the store does not contain data, the problem is that the store load is asyncronous therefore when you count the store records, the store is actualy empty.
To 'fix' this, use the callback method of the store load.
currentUser.load({
scope : this,
callback: function(records, operation, success) {
//here the store has been loaded so you can use what functions you like
currentUser.count();
}
});
All the sencha examples have the proxies in the store, but you should actually put the proxy in the model, so that you can use the model.load method. the store inherits the model's proxy, and it all works as expected.
it looks like model.load hardcodes the id though (instead of using idProperty), and it always has to be an int, as far as I can tell.
good luck!

Categories

Resources