Backbone.js fetch unknown error - backbone.js

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));
}

Related

Getting view to update on save using Backbone.js

I am learning Backbone.js and as a trial project I am creating a little WordPress user management application. So far my code shows a listing of all WordPress users and it has a form which enables you to add new users to the application.
This all works fine however when you add a new user the listing of users doesn't update automatically, you need to refresh the page to see the new user added which isn't ideal and defeats one of the benefits of Backbone.js!
I have a model for a user and then a collection which compiles all the users. I have a view which outputs the users into a ul and I have a view which renders the form. How do I make my code work so when the .save method is called the view which contains the users updates with the new user? Or is there another way to approach this?
//define the model which sets the defaults for each user
var UserModel = Backbone.Model.extend({
defaults: {
"username": "",
"first_name": "",
"last_name": "",
"email": "",
"password": "",
},
initialize: function(){
},
urlRoot: 'http://localhost/development/wp-json/wp/v2/users'
});
//define the base URL for ajax calls
var baseURL = 'http://localhost/development/wp-json/wp/v2/';
//function to define username and password
function authenticationDetails(){
var user = "myUserName";
var pass = "myPassword";
var token = btoa(user+':'+pass);
return 'Basic ' + token;
}
//add basic authorisation header to all API requests
Backbone.$.ajaxSetup({
headers: {'Authorization':authenticationDetails()}
});
//create a collection which returns the data
var UsersCollection = Backbone.Collection.extend(
{
model: UserModel,
// Url to request when fetch() is called
url: baseURL + 'users?context=edit',
parse: function(response) {
return response;
},
initialize: function(){
}
});
// Define the View
UserView = Backbone.View.extend({
model: UserModel,
initialize: function() {
// create a collection
this.collection = new UsersCollection;
// Fetch the collection and call render() method
var that = this;
this.collection.fetch({
success: function () {
that.render();
}
});
},
// Use an external template
template: _.template($('#UserTemplate').html()),
render: function() {
// Fill the html with the template and the collection
$(this.el).html(this.template({ users: this.collection.toJSON() }));
return this;
},
});
var userListing = new UserView({
// define the el where the view will render
el: $('#user-listing')
});
NewUserFormView = Backbone.View.extend({
initialize: function() {
this.render();
},
// Use an external template
template: _.template($('#NewUserTemplate').html()),
render: function() {
// Fill the html with the template and the collection
$(this.el).html(this.template());
return this;
},
events: {
'click .create-user':'addNewUser'
},
addNewUser: function(){
var newFirstName = $('.first-name').val();
var newLastName = $('.last-name').val();
var newEmail = $('.email').val();
var newPassword = $('.password').val();
var newUserName = newFirstName.toLowerCase();
var myNewUser = new UserModel({username:newUserName,first_name:newFirstName,last_name:newLastName,email:newEmail,password:newPassword});
console.log(myNewUser);
myNewUser.save({}, {
success: function (model, respose, options) {
console.log("The model has been saved to the server");
},
error: function (model, xhr, options) {
console.log("Something went wrong while saving the model");
}
});
}
});
var userForm = new NewUserFormView({
// define the el where the view will render
el: $('#new-user-form')
});
All backbone objects (models, collections, views) throw events, some of which would be relevant to what you want. Models throw change events when their .set methods are used, and Collections throw add or update events... a complete list is here.
Once you know which events are already being thrown, you can listen to them and react. For example, use listenTo - in your view's initialize, you can add:
this.listenTo(this.collection, 'add', this.render);
That will cause your view to rerender whenever a model is added to your collection. You can also cause models, collections, whatever, to throw custom events using trigger from anywhere in the code.
EDIT: For the specific case of getting your user listing view to rerender when a new user is added using the form, here are the steps you can take... In the initialize method of your UserView, after the initialize the collection, add:
this.listenTo(this.collection, 'add', this.render);
Then in your form view... assuming you want to wait until the save is complete on your server, in the addNewUser method, in the success callback of your save, add:
userlisting.collection.add(model);
This will work, since the instance of your UserView is in the global scope. Hope this one works for you!

Backbone error: Model is not a constructor

Afternoon all, I'm relatively new to backbone and have been stumped for 3 days with this error which I have not seen before.
I have a collection 'TestCollection' which defines it's model as a function. When the collection is loaded I get an error the first time it attempts to make a model with class 'TestModel'.
The error I get is:
Uncaught TypeError: TestModel is not a constructor
at new model (testCollection.js:14)
at child._prepareModel (backbone.js:913)
at child.set (backbone.js:700)
at child.add (backbone.js:632)
at child.reset (backbone.js:764)
at Object.options.success (backbone.js:860)
at fire (jquery.js:3143)
at Object.fireWith [as resolveWith] (jquery.js:3255)
at done (jquery.js:9309)
at XMLHttpRequest.callback (jquery.js:9713)
I believe I have given both the collection and the model all of the code they should need to work. It feels like something has gone wrong with the loading, but when I put a console.log at the top of the model file I could see that it is definitely being loaded before the collection attempts to use it.
Any help would be massively appreciated.
TestCollection:
define([
'backbone',
'models/testModel'
], function(Backbone, TestModel) {
var TestCollection = Backbone.Collection.extend({
model: function(attrs) {
switch (attrs._type) {
case 'test':
console.log('making a test model')
return new TestModel();
}
},
initialize : function(models, options){
this.url = options.url;
this._type = options._type;
this.fetch({reset:true});
}
});
return TestCollection;
});
TestModel:
require([
'./testParentModel'
], function(TestParentModel) {
var TestModel = TestParentModel.extend({
urlRoot: 'root/url',
initialize: function() {
console.log('making test model')
}
});
return TestModel;
});
File where TestCollection is made:
define(function(require) {
var MyProjectCollection = require('collections/myProjectCollection');
var TestCollection = require('collections/testCollection');
Origin.on('router:dashboard', function(location, subLocation, action) {
Origin.on('dashboard:loaded', function (options) {
switch (options.type) {
case 'all':
var myProjectCollection = new MyProjectCollection;
myProjectCollection.fetch({
success: function() {
myProjectCollection.each(function(project) {
this.project[project.id] = {};
this.project[project.id].testObjects = new TestCollection([], {
url: 'url/' + project.id,
_type: 'test'
});
});
}
});
}
});
});
I've had a look around stack overflow, it does not appear to be the issue below (which seems to be the most common issue).
Model is not a constructor-Backbone
I also do not think I have any circular dependencies.
Any help would be massively appreciated as I am completely stumped. I've tried to include only the relevant code, please let me know if additional code would be useful.
Thanks
I can't say for other parts of the code but an obvious problem you have is misunderstanding what data is passed to the model creator function.
var TestCollection = Backbone.Collection.extend({
model: function(attrs) {
switch (attrs._type) { // attrs._type does exist here
console.log( attrs ); // Will print { foo: 'bar' }
case 'test': // This will always be false since attrs._type does not exist
console.log('making a test model')
return new TestModel();
default:
return new Backbone.Model(); // Or return some other model instance,
// you MUST have this function return
// some kind of a Backbone.Model
}
},
initialize : function(models, options){
this.url = options.url;
this._type = options._type;
this.fetch({reset:true});
}
});
new TestCollection([ { foo: 'bar' }], {
url: 'url/' + project.id,
_type: 'test' // This will NOT be passed to the model attrs, these are
// options used for creating the Collection instance.
})
To re-iterate. When you instantiate a Collection you pass an array of plain objects [{ foo: 'bar'}, { foo: 'baz'}] ( or you get them via fetch like you're doing ). That object will be passed as the attrs parameter in the model function, and the model function MUST return at least some kind of a Backbone.Model instance so you need a fallback for your switch statement.

How to dynamically update a Marionette CollectionView when the underlying model changes

Seems like this should be obvious, but there seem to be so many different examples out there, most of which cause errors for me, making me think they are out of date. The basic situation is that I have a MessageModel linked to a MessageView which extends ItemView, MessageCollection linked to a MessageCollectionView (itemView: MessageView). I have a slightly unusual scenario in that the MessageCollection is populated asynchronously, so when the page first renders, it is empty and a "Loading" icon would be displayed. Maybe I have things structured incorrectly (see here for the history), but right now, I've encapsulated the code that makes the initial request to the server and receives the initial list of messages in the MessageCollection object such that it updates itself. However, I'm not clear, given this, how to trigger displaying the view. Obviously, the model shouldn't tell the view to render, but none of my attempts to instantiate a view and have it listen for modelChange events and call "render" have worked.
I have tried:
No loading element, just display the CollectionView with no elements on load, but then it doesn't refresh after the underlying Collection is refreshed.
Adding modelEvents { 'change': 'render' } to the view --> Uncaught TypeError: Object function () { return parent.apply(this, arguments); } has no method 'on'
I also tried this.bindTo(this.collection..) but "this" did not nave a bindTo method
Finally, I tried, in the view.initialize: _.bindAll(this); this.model.on('change': this.render); --> Uncaught TypeError: Object function () { [native code] } has no method 'on'
Here is the code
Entities.MessageCollection = Backbone.Collection.extend({
defaults: {
questionId: null
},
model: Entities.Message,
initialize: function (models, options) {
options || (options = {});
if (options.title) {
this.title = options.title;
}
if (options.id) {
this.questionId = options.id;
}
},
subscribe: function () {
var self = this; //needed for proper scope
QaApp.Lightstreamer.Do('subscribeUpdate', {
adapterName: 'QaAdapter',
parameterValue: this.questionId,
otherStuff: 'otherstuff',
onUpdate: function (data, options) {
console.log("calling sync");
var obj = JSON.parse(data.jsonString);
self.set(obj.Messages, options);
self.trigger('sync', self, obj.Messages, options);
}
});
},
});
Views.MessageCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: Views.MessageView,
tagName: 'ul',
// modelEvents: {
// 'change': 'render'
// },
onAfterItemAdded: function (itemView) {
this.$el.append(itemView.el);
}
});
var Api = {
subscribe: function (id) {
var question = new QaApp.Entities.Question(null, { id: id });
question.subscribe();
var questionView = new QaApp.Views.QuestionView(question);
QaApp.page.show(questionView);
}
};
I am very grateful for all the help I've received already and thanks in advance for looking.
Try this:
var questionView = new QaApp.Views.QuestionView({
collection: question
});

Connection between model and collection in backbone and parse.com

i'm trying to connect model and collection using parse.com but i'm confused. I'm tring to fetch by collection using backbone and javascript api parse.com but compare this error:POST https://api.parse.com/1/classes 404 (Not Found).
Model:
var Person = Backbone.Model.extend({
defaults:{
},
initialize:function(){
console.log("inperson");
this.validate();
this.send();
},
validate:function(){
console.log("validate");
},
send:function(){
var user = new Parse.User();
user.set("username", this.get("username"));
user.set("password", this.get("password"));
user.set("email", this.get("email"));
user.signUp(null, {
success: function(user) {
// Hooray! Let them use the app now.
},
error: function(user, error) {
// Show the error message somewhere and let the user try again.
alert("Error: " + error.code + " " + error.message);
}
});
}
});
return Person;
});
Collection:
var Usercollection = Parse.Collection.extend({
model:Person,
initialize:function(){
}
});
return Usercollection;
});
and finally the view that call the colletion and fetch:
var HomeView = Backbone.View.extend({
template: Handlebars.compile(template),
events: {
},
initialize: function() {
console.log("inhomeview");
var amici = new Usercollection();
amici.fetch({
success: function(collection) {
amici.each(function(object) {
console.warn(object);
});
},
error: function(amici, error) {
// The collection could not be retrieved.
}
});
},
render: function() {
}
});
return HomeView;
});
Cant you just swap the backbone collection and model to Parse's ones? (You only used the Parse type of the collection, not the model!)
Try switch that Backbone model to a Parse.Object .
Step by step below:
First of all Lets create a new app on Parse.com, mine is called FunkyAppartments.
Insert the script tag for loading Parse javascript lib into index.html or whathever:
<script src="http://www.parsecdn.com/js/parse-1.5.0.min.js"></script>
Switch the backbone model and collection to use parse types instead (and rename the fetch method if you have extended backbones, since we do not want to overide the one of parse):
//var Appartment = Backbone.Model.extend(); Backbone wo. Parse.com
var Appartment = Parse.Object.extend("Appartment");
//var Appartments = Backbone.Collection.extend({ Backbone wo. Parse.com
var Appartments = Parse.Collection.extend({
model: Appartment,
loadAppartments: function(callback){
debugger;
this.query = new Parse.Query(Appartment);
this.fetch();
}
});
I added a debugger tag in the load appartments so that developer tools breaks in the middle of the controller, here I have access to the Appartment private type of the controller, hence i can store some data on the parse server and verify by pasting the below in the developer tools console.
var testAppartment = new Appartment();
testAppartment.save({name: "foobars"}).then(function(object) {
alert("yay! it worked");
});
Yei, the data shows up in the parse.com UI for the app we just added there. And more importantly it shows up in our frontend. That was easy!
UPDATE: PROBLEMS W BACKBONE 1.2.1, MARIONETTE 2.4.2, UNDERSCORE 1.8.3
I noticed that I actually had been using old versions of marionette, backbone and underscore.js. An initial update appeared to break the application.
After some research i found that it was the parse part that did not return objects that would successfully render. Hence I changed the collection type back to an extension of: Backbone.collection instead of Parse.collection.
I also had to override the query method, since the objects would not save on the correct id, updating an object resulted in a new object being added instead of an old one being updated.
var Apartment = Parse.Object.extend('Appartment');
var Apartments = Backbone.Collection.extend({
model: Apartment,
query: new Parse.Query(Apartment),
initialize: function(){
MyApp.vent.on('search:param', function(param){self.search(param); });
var self = this;
this.query.find({
success: function(results){
self.reset();
results.forEach(function(result){
result.attributes.id__ = result.id
var ap = new Apartment(result.attributes);
self.add(ap);
});
}
});
}
});
I added an attribute: id__ to hold the parse id (naming it just id did not work since it backbone interfered with it, making it disappear).
Finally in saving the model to parse i utilized id__ as id in the save call:
var ApartmentEditView = Backbone.Marionette.ItemView.extend({
template: "#apartment-edit-template",
className: "apartmentDetail",
events: {
"click .store": "storeEdit",
"click .close": "closeEdit"
},
storeEdit: function(){
var priceNum = Number($('#price_field').val().replace(/\s/g, ''));
this.model.set({
id: this.model.attributes.id__,
name:$('#name_field').val(),
price:priceNum,
description:$('#desc_field').val(),
url:$('#url_field').val()
});
this.model.save();
this.closeEdit();
},
closeEdit: function(){
var detailView = new ApartmentDetailView({model: this.model});
MyApp.Subletting.layout.details.show(detailView);
}
});
Now the object is updated correctly in the database.

Laravel 4 and Backbone not playing well together -- JSON improperly formatted

I'm trying to return JSON output from a Laravel route. Here's my route:
Route::get('main-contact-count', function() {
$mc = MainContact::where('flag', '=', '1')->count();
return Response::json(['count' => $mc]);
});
If I look at the response tab in Firebug, I'm getting back:
{"count":9}
The JSON encoding is missing the square brackets. This JSON is different than the JSON Laravel returns from a resourceful controller. It has the square brackets. Backbone parses it fine. Backbone doesn't parse the above JSON correctly. If you look at the length and models when you console.log a collection, they're both zero. You can, however, drill down into the object and you can find a count property set to 9.
How do I get the correct JSON out of Laravel?
Just for kicks and giggles, I'm posting my Backbone, in case I'm doing something hinky there:
App.Collections.Count = Backbone.Collection.extend({
model: App.Models.Count,
initialize: function(models, options) {
this.fetch({
success: function(data, options) {
// console.log(data.models);
}
});
if (options) {
this.url = this.url || options.url;
}
}
});
App.Views.Count = Backbone.View.extend({
tagName: 'span',
className: 'leadCount',
template: _.template($('#contactCount').html()),
initialize: function() {
},
render: function() {
this.$el.html(this.template(this.collection.toJSON()));
return this;
}
});
And in my route:
var mc = new (App.Collections.Count.extend({ url: 'main-contact-count' }))();
var mcv = new (App.Views.Count.extend({ collection: mc }))();
The JSON response sent by your service is a single JSON object ({...}) Your collection is not able to parse it because it expects an array of JSON objects ([{...}, {...}]).
If I've understood your code correctly, /main-contact-count is a specialized service whose function is to return and single datum: a count of contacts. If this is the case, Backbone.Collection may not be the correct solution. Collections are meant for... well, collections of things.
You should use just a model instead:
App.Models.Count = Backbone.Model.extend({
url:'main-contact-count'
});
var countModel = new App.Models.Count();
countModel.fetch({success: function(model, resp) {
var count = model.get('count');
});
Or even better, forego Backbone altogether and just fetch the data using jQuery:
$.getJSON('main-contact-count', function(response) {
var count = response.count;
});
try:
return Response::json(array(array('count' => $mc));

Resources