Issue with backbonejs model objects json representation - backbone.js

I have a question related to contained backbonejs model objects.
Using yeoman backbone generator described at https://github.com/yeoman/generator-backbone, I created a backbone application to test how a model object that contains another model object is being sent as json to the backend server.
I added two models contact and address to this test application.
yo backbone:model contact
yo backbone:model address
Contact object contains one address object. Contact and address backbone model objects are shown below. The init function in the generated main.js is also shown below. The console log is shown below. Ignore the POST error since there is no endpoint contacts.
My question is: in the Chrome browser Developer Tools window, network tab the request payload is:
{"name":"John Doe"}
What changes are needed to the backbone model objects so that the request payload will have the contained address object also? Thanks. I want the payload to look like this:
{
"name": "John Doe",
"address": {
"addressLine1": "Somewhere"
}
}
From console in the Developer Tools window, I can confirm addressLine1 is 'Somewhere' in the contained address object :
Inside Contact.initialize
address.js:14 Inside Address.initialize
contact.js:24 Inside Contact.getAddress
main.js:12 Hello from Backbone! name = John Doe addressLine1 = Somewhere
contact.js:21 Inside Contact.validate
main.js:22 return value from save [object Object]
jquery.js:8630 POST http://localhost:9001/contacts 404 (Not Found)
...
main.js:19 Error [object Object]
From main.js
init: function () {
'use strict';
var myContact = new Test.Models.Contact();
console.log('Hello from Backbone! name = ' + myContact.get('name') + ' addressLine1 = ' + myContact.getAddress().get('addressLine1'));
var rv = myContact.save(null,{
success: function(response) {
console.log('Success ' + response);
},
error: function(response) {
console.log('Error ' + response);
}
});
console.log ('return value from save ' + rv);
}
};
From contact.js
/*global Test, Backbone*/
Test.Models = Test.Models || {};
(function () {
'use strict';
Test.Models.Contact = Backbone.Model.extend({
url: '/contacts',
initialize: function() {
console.log ('Inside Contact.initialize');
this.address=new Test.Models.Address();
},
defaults: {
name: 'John Doe'
},
validate: function(attrs, options) {
console.log ('Inside Contact.validate');
},
getAddress: function() {
console.log ('Inside Contact.getAddress');
return this.address;
},
parse: function(response, options) {
console.log ('Inside Contact.parse');
return response;
}
});
})();
From address.js
/*global Test, Backbone*/
Test = {}; // a global object
Test.Models = Test.Models || {};
(function () {
'use strict';
Test.Models.Address = Backbone.Model.extend({
url: '',
initialize: function() {
console.log ('Inside Address.initialize');
},
defaults: {
addressLine1: 'Somewhere'
},
validate: function(attrs, options) {
console.log ('Inside Address.validate');
},
parse: function(response, options) {
console.log ('Inside Address.parse');
return response;
}
});
})();
Call stack when I set a breakpoint at contact validate function:
validate (contact.js:21)
_validate (backbone.js:568)
save (backbone.js:465)
init (main.js:14)
(anonymous) (main.js:29)
fire (jquery.js:3099)
fireWith (jquery.js:3211)
ready (jquery.js:3417)
completed (jquery.js:3433)
When the above break point is triggered, I can verify this object has address information:
this
child {cid: "c1", attributes: {…}, _changing: false, _previousAttributes: {…}, changed: {…}, …}
address:child
attributes:{addressLine1: "Somewhere"}
changed:{}
cid:"c2"
_changing:false
_pending:false
_previousAttributes:{}
__proto__:Backbone.Model
attributes:{name: "John Doe"}
changed:{}
cid:"c1"
_changing:false
_pending:false
_previousAttributes:{}
__proto__:Backbone.Model
Thanks a lot.

I saw Nesting collection or models within another model
I added the following line of code just before I save the model in main.js:
myContact.set('address', myContact.getAddress().toJSON());
then I can verify the request payload is what I was expecting:
{"name":"John Doe","address":{"addressLine1":"Somewhere"}}
Thanks to Emile Bergeron for pointing out toJSON() method must be called.
The take away for me is backbone is bare bones. It is not smart enough to figure out an object may contain other objects and do the needful. The javascript programmer must call toJSON() at appropriate time.

Related

Testing Angular-Resource: Expected an Object, got a Resource

When trying to test some simple angular code using $resource, I end up with a Resource object which contains a $promise key and hence a failure of the form: Failure/Error: Expected Resource(...) to equal Object(...)
I was expecting to get back the object I passed to the respond method as part of httpBackend.expectGET('/books/5.json').respond(my_book). Am I using $resource wrong or is something wrong in my test?
Code
var bookApp = angular.module('bookApp',
[
'ngResource',
]
);
function BookController(scope, $resource) {
var ctrl = this;
var Book = $resource('/books/:selected.json', {selected:'#id'});
ctrl.fetch_book = function(id){
console.log('Selecting options ' + id);
Book.get({selected:id}, function(data){
console.log('Received: ' + JSON.stringify(data));
ctrl.current_book = data;
});
};
}
BookController.$inject = ['$scope', '$resource'];
bookApp.component('book', {
controller: BookController
});
Test
describe('component: tree', function() {
var component_controller, $componentController, httpBackend, my_book;
beforeEach(module('bookApp'));
beforeEach(inject(function($httpBackend, _$componentController_) {
httpBackend = $httpBackend;
$componentController = _$componentController_;
}));
describe('$ctrl.fetch_book(book_id)', function(){
beforeEach(function() {
component_controller = $componentController('book');
my_book = {title: 'Sanctuary', id: '5'};
});
it('fetches the book with id=book_id', function(){
httpBackend.expectGET('/books/5.json').respond(my_book);
component_controller.fetch_book(5);
httpBackend.flush();
console.log('Options: ' + JSON.stringify(component_controller.current_book));
console.log('constructor: ' + JSON.stringify(component_controller.current_book.constructor.name));
expect(component_controller.current_book).toEqual(my_book);
});
});
});
Result
$ bundle exec teaspoon -f documentation
component: tree
$ctrl.fetch_book(book_id)
fetches the book with id=book_id (FAILED - 1)
# Selecting options 5
# Received: {"title":"Sanctuary","id":"5"}
# Options: {"title":"Sanctuary","id":"5"}
# constructor: "Resource"
Failures:
1) component: tree $ctrl.fetch_book(book_id) fetches the book with id=book_id
Failure/Error: Expected Resource({ title: 'Sanctuary', id: '5',
$promise: Promise({ $$state: Object({ status: 1, value:
<circular reference: Object> }) }), $resolved: true }) to equal
Object({ title: 'Sanctuary', id: '5' }).
Finished in 0.02600 seconds
1 example, 1 failure
Try this in your tester:
expect(component_controller.current_book).toEqual(angular.toJSON(my_book));
It'll strip the object's properties and you'll have a match.
You can also try angular.equals but I haven't tested that.
Try adding the following to your spec file and see if it works. I saw it in the PhoneCat example and it worked for me.
beforeEach(function() {
jasmine.addCustomEqualityTester(angular.equals);
});
You can try doing something like this:
expect(component_controller.current_book.toJSON()).toEqual(my_book);
I had the same issue where I got an error of
Expected object to be a kind of Object, but was Resource(
This is what I had before:
expect(self.project).toEqual(mockProject);
And after I added .toJSON() it was all good:
expect(self.project.toJSON()).toEqual(mockProject);
Hope this helps!

AngularJs change location after save

I'm having trouble changing location after I save a new customer in an angular.
Here's my controller.
function CustomersNewController($scope, $location, Customer) {
$scope.selected_customer = '';
$scope.submit = function() {
rails_customer = new Customer({customer: $scope.new_customer});
rails_customer.$save({format: 'json'},function(data, getResponseHeaders) {
$location.path(data.id + '/edit/') ;
}, function(data, getResponseHeaders) {
console.log("ERROR");
});
};
}
The customer saves to the database fine. The problem is that I can't get the id of the object that was just saved in order to redirect to the edit page.
The 'data' object is populated but it looks like this:
b {customer: Object, $then: function, $resolved: true, $get: function, $save: function…}
$resolved: true
$then: function (callback, errback) {
customer: Object
Can anyone give me a hand on how to dig the id out of the returned data?
Thanks!
According to your data object it should be
$location.path(data.customer.id + '/edit');

Backbone "at()" is returning undefined as result in Firebug

I'm trying to follow a screencast on how to return a result from a database using Backbone.js and REST. My RESTful service (at least I think it's RESTful -- REST is new to me) is serving up JSON data like you would want. Everything appears to work fine until the last step when I try to access the data using at(). Here is my code.
This is my Backbone:
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.template = function(id) {
return _.template( $('#' + id).html());
};
var vent = _.extend({}, Backbone.Events);
App.Router = Backbone.Router.extend({
routes: {
'' : 'index',
'*other' : 'other'
},
index: function() {
},
other: function() {
}
});
App.Models.Main = Backbone.Model.extend({
defaults : {
FName: ''
}
});
App.Collections.Mains = Backbone.Collection.extend({
model: App.Models.Main,
url: '../leads/main_contact'
});
new App.Router;
Backbone.history.start();
})();
In the Firebug console, which is what's used in Jeffrey Way's screencast, I type the following:
mains = new App.Collections.Mains();
mains.fetch();
mains.toJSON();
That works fine. I can use Firebug to see that there is the proper data there. Here is my result:
[Object { id="1023", timestamp="2012-05-16 08:09:30", FName="Eulàlia", more...},...
But when I try to access the object with the id of 1023, I get "undefined." I do this:
mains.at(1023).get('FName');
What am I doing wrong?
the at method retrieves an element at a specific index in the collection.
So if you want to get the element at position 1023, you need 1023 items in your collection. Which you probally don't have.
The id that you have and set to 1023 has nothing to do with index in that collection.

Backbone.js fetch unknown error

I'm facing an issue with my backbone.js app: I'm trying to fetch data from a JSON webservice, the GET HTTP request is successfull (i had a look in the developer console of chrome) but backbone fetch trigger an error and doesn't update the model.
You can have a look here to the code:
https://github.com/tdurand/faq-app-client-mobile
And you can run the app and try to debug here:
http://tdurand.github.com/faq-app-client-mobile/
The JSON Feed is like this
[
{
"title":"Probleme ou Bug",
"desc":"Pour les problemes ou les bugs rencontrés",
"entries":[
{
"title":"testdqs",
"desc":"testqsdqs"
}
]
}
]
My collection model is:
var Categories = Backbone.Collection.extend({
url:"http://cryptic-eyrie-7716.herokuapp.com/faq/fr",
model:Category,
parse:function(response) {
console.log("test")
console.log(response);
return response;
},
sync: function(method, model, options) {
var params = _.extend({
type: 'GET',
dataType: 'jsonp',
url: model.url,
processData:false
}, options);
return $.ajax(params);
},
});
And my view:
var IndexView = Backbone.View.extend({
el: '#index',
initialize:function() {
Categories.fetch({
success: function(m,r){
console.log("success");
console.log(r); // => 2 (collection have been populated)
},
error: function(m,r) {
console.log("error");
console.log(r.responseText);
}
});
Categories.on( 'all', this.render, this );
},
//render the content into div of view
render: function(){
//this.el is the root element of Backbone.View. By default, it is a div.
//$el is cached jQuery object for the view's element.
//append the compiled template into view div container
this.$el.html(_.template(indexViewTemplate,{categories:Categories}));
//Trigger jquerymobile rendering
$("#index").trigger('pagecreate');
//return to enable chained calls
return this;
}
});
return IndexView;
Thanks a lot for your help
From what I see, you're not making an instance of your collection anywhere. Javascript isn't really object oriented and it has this weird functions-as-classes and prototype -inheritance type of deal that can confuse anyone coming from the OO -world.
var Categories = Backbone.Collection.extend...
What happens above is that you extend the Backbone Collection's (which is a function) prototype properties with the object (or dictionary) you define. To be able to instantiate this 'class' to an object, you need to use the new keyword. You don't do this, so you are calling the function fetch of the 'class' Categories, which will produce unexpected results.
So instantiate your collection before fetching:
initialize:function() {
this.collection = new Categories(); // instantiate the collection
// also set event handlers before producing events
this.collection.on( 'all', this.render, this );
this.collection.fetch({
success: function(m,r){
console.log("success");
console.log(r); // => 2 (collection have been populated)
},
error: function(m,r) {
console.log("error");
console.log(r.responseText);
}
});
}
Hope this helps!
I found my mistake.
My backend server (with play! framework), was not rendering JSONP properly
This is the code i now use to do it if someone has the same issue.
//Render JSONP
if (request.params._contains("callback")) {
Gson gson = new Gson();
String out = gson.toJson(categoryJSON(categories,lang));
renderText(request.params.get("callback") + "(" + out + ")");
} else {
renderJSON(categoryJSON(categories,lang));
}

populating nested collection with nested json

Solution
in my route
Myapp.Routes = Backbone.Router.extend({
init: function(){
user = new User();
user.fetch({user,
success: function(response){
user.classlist = new classes(response.attributes.classes);
});
}
});
I've got a serialized json array being returned from my server, and I am trying to put the nested objects into my nested collections.
This answer, I thought was going to get me there, but I'm missing something.
How to build a Collection/Model from nested JSON with Backbone.js
The json which I am trying to populate my nested model with is
{first_name: "Pete",age: 27, classes: [{class_name: "math", class_code: 42},{class_name: "french", class_code: 18}]}
I create my user model
MyApp.Models.Users = = Backbone.Model.extend({
initialize: function(){
this.classlist = new MyApp.Collections.ClassList();
this.classlist.parent = this;
}
});
I had tried to follow the example on the other page, and use
this.classlist = new MyApp.Collections.ClassList(this.get('classes'));
this.classlist.parent = this;
but this.get('classes') returns undefined.
I've also tried getting the classes array through this.attributes.classes, but that is also undefined.
------------updated to include re-initialize --------------------
The function where I am initializing the user and classes is in the User routes and is called re-initialize. I use this function to fetch the user and their classes and store the object.
re_initialize: function(id){
user = new MyApp.Models.User();
MyApp.editingClasses.url = 'classes/'+id;
MyApp.editingClasses.fetch({
success: function(response){
MyApp.editingClasses.parse(response);
}
});
new MyApp.Views.ClassesInput();
},
As you can see, I'm calling the parse explicitly in the success function, but it isn't adding the classes to the collection.
I can't include the 'collection' because for some reason I can't access it in backbone.
the user model, after getting returned to backbone includes the classes array, which I am trying to put into the ClassList collection.
The user model object copied from the javascript terminal looks like this.
attributes: Object
created_at: "2012-01-05T16:05:19Z"
id: 63
classes: Array[3]
0: Object
created_at: "2012-01-18T20:53:34Z"
id: 295
teacher_id: 63
class_code: 42
updated_at: "2012-01-18T20:53:34Z"
class_name: math
__proto__: Object
1: Object
2: Object
length: 3
__proto__: Array[0]
You can use the parse function to pre-process the server response:
MyApp.Models.Users = Backbone.Model.extend({
parse: function(response) {
var classesJSON = response.classes;
var classesCollection = MyApp.Collections.ClassList(classesJSON);
response.classes = classesCollection;
return response;
}
});
var user = new MyApp.Models.Users();
user.fetch();
// You should now be able to get the classlist with:
user.get('classes');
That said, the approach suggested in the other question should also work. It could be that when your initialize function is called, the model hasn't yet been populated with the data?
For example, if you're doing:
var user = new MyApp.Models.Users();
It won't have any attributes yet to give to the classlist collection. Could that be your problem?
Okay! you can maybe fetch the classes this way :
Model :
window.person = Backbone.Model.extend({
defaults: { }
});
Collection :
window.ClassesCollection = Backbone.Collection.extend({
model: person,
url: "http://your/url/data.json",
parse: function(response){
return response.classes;
}
});
Router :
window.AppRouter = Backbone.Router.extend({
routes: {
"" : "init"
},
init: function(){
this.classesColl = new ClassesCollection();
this.classesColl.fetch();
this.classesView = new ClassesView({collection: this.classesColl});
}
});
View : (for rendering every classes)
window.ClassesView = Backbone.View.extend({
el: $('...'),
template: _.template($("...").html()),
initialize: function() {
this.collection.bind("reset", this.render, this);
},
render: function(collection) {
_.each( collection.models, function(obj){
...
//obj.get('class_name') or obj.get('class_code')
...
}, this );
...
return this;
}
});

Resources