Backbone performing a GET immediately after a POST - backbone.js

I'm experimenting for the first time with backbone.js and I have a very simple Grails application with a single domain called Book. Things seem to be working well however, I've noticed that when I POST the data from the form to the server backbone then does a GET to the server with the ID of the new record. However, the POST returns the results as JSON and populates the table accordingly. I'm not sure I understand the need for the GET following the POST or how to stop this from happening.
$(function() {
// Model
window.Book = Backbone.Model.extend({
url: function() {
return this.id ? '/BackboneTest/books/' + this.id : '/BackboneTest/books.json';
},
defaults: { book: {
title: 'None entered',
description: 'None entered',
isbn: 'None entered'
}},
initialize: function() {
// can be used to initialize model attributes
}
});
// Collection
window.BookCollection = Backbone.Collection.extend({
model: Book,
url: '/BackboneTest/books.json'
});
window.Books = new BookCollection;
//View
window.BookView = Backbone.View.extend({
tagName: 'tr',
events: {
// can be used for handling events on the template
},
initialize: function() {
//this.render();
},
render: function() {
var book = this.model.toJSON();
//Template stuff
$(this.el).html(ich.book_template(book));
return this;
}
});
// Application View
window.AppView = Backbone.View.extend({
el: $('#book_app'),
events: {
"submit form":"createBook"
},
initialize: function() {
_.bindAll(this, 'addOne', 'addAll');
Books.bind('add', this.addOne);
Books.bind('refresh', this.addAll);
Books.bind('all', this.render);
Books.fetch();
},
addOne: function(book) {
var view = new BookView({model:book});
this.$('#book_table').append(view.render().el);
},
addAll: function() {
Books.each(this.addOne);
},
newAttributes: function(event) {
return { book: {
title: $('#title').val(),
description: $('#description').val(),
isbn: $('#isbn').val()
} }
},
createBook: function(e) {
e.preventDefault();
var params = this.newAttributes(e);
Books.create(params)
//TODO clear form fields
}
});
// Start the backbone app
window.App = new AppView;
});

I've determined that the cause of this was server side. Because of some scaffolded code that got generated for testing purposes, on the save, there was an additional redirect which resulted in a 302. This caused the GET after the POST. Once I cleaned up the server side code, I only get the POST, as expected.

Backbone usesPOST as a factory (getting the id from the server) with:
a payload request { title: 'None entered' }
a response { id: 12, title: 'None entered' }
It seems that your code trigger a GET action after the POST success. The code Books.bind('all', this.render) do not seems to be related to anything. It is not binded like add and there is no such method in the View.

Related

Backbone: Setting server response from one model to another

After login user gets redirected to another page. So the response Login model gets from server, it tries to set to another model.
Second model gets set properly from the first model.But when it reaches another page's view, it becomes empty.
Models
var LoginModel = Backbone.Model.extend({
url:'http://localhost:3000/login',
defaults: {
email:"",
password:""
},
parse: function(resp) {
console.log('Model: Got the response back');
return resp;
},
login: function() {
console.log('Model: Login function:'+JSON.stringify(this));
this.save(
{}, {
success: function(resp) {
console.log('success'+JSON.stringify(resp));
dashboardModel.set(resp.result);
window.location = 'templates/dashboard.html'
},
error: function(error) {
console.log('error: '+JSON.stringify(error));
}
});
},
redirect: function() {
console.log('inside redirect method');
}
});
var loginModel = new LoginModel();
var DashboardModel = Backbone.Model.extend({
defaults: {
campaignName:"",
orderedAndGoal:"",
status:"",
endDate:"",
},
parse: function(resp) {
console.log('Model: Got the response back');
return resp;
}
});
var dashboardModel = new DashboardModel();
View
var DashboardView = Backbone.View.extend({
template:_.template('<div>'+
'<h3><%= campaignName %></h3>'+
'<span><%= orderedAndGoal %>, </span>'+
'<span><%= status %>, </span>'+
'<span><%= endDate %>, </span>'+
'</div>'),
initialize: function() {
this.model.on('change', this.render, this);
},
render: function() {
console.log('what happens here')
var attributes = this.model.toJSON();
this.$el.html(this.template(attributes));
},
});
var dashboardView = new DashboardView({model: dashboardModel});
dashboardView.render();
$(".container").append(dashboardView.el);
You are literally navigating to another HTML page with window.location = .... That's not gonna work. When the browser navigates to another page, all your running code and any variables they set are blown away. Backbone is all about creating "single page applications (SPA)" where there is only 1 page loaded by the browser and then the DOM is dynamically changed at runtime. Take a look at Backbone.Router as a starting point for understanding this. You'll call methods on this router to move the user to another "view" rather than touching window.location
Fix that and your code should work :)

edit - backbone.js beginner attempting to load data

Attempting to post a beginner question but not making it past the "quality standards" filter.
I read through the thread on the error message. Are beginner questions too basic? Understandable but it might be better to put that in the error message. I include my code and my English is correct outside of the code block. The question has not been addressed, or at least is not returned from various search patterns. Is there anyway to appeal the filter as multiple rewrites have not cleared the hurdle or solved my, admittedly beginner, problem? In a last ditch attempt to hack the filter I'm pasting my original question in bellow with a few English only edits. edit - this seems to have worked but leaving above paragraph in to not jinx it.
Trying to load data into backbone but the render function is not firing.
Firebug shows the GET is arriving with the correct data string.
I have attempted to assign "this" to a variable and fire as a function but still no luck.
(function($) {
var DObj = Backbone.Model.extend({
defaults: {
dstring: 'dstring again'
},
});
var MObs = Backbone.Collection.extend({
defaults: {
model: DObj
},
model: DObj,
url: 'scr/bbone.php'
});
var MView = Backbone.View.extend({
initialize: function() {
this.collection = new MObs();
//this.collection.bind("reset", this.render, this);
this.collection.on("sync", this.render, this);
//this.collection.bind("change", this.render, this);
this.collection.fetch();
},
render: function() {
alert("here : ");
}
});
var newMob = new MView();
})(jQuery);
I have rewritten the code with mockjax to mock response from the server, here it is:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery-mockjax/1.5.1/jquery.mockjax.min.js"></script>
<script>
var DObj = Backbone.Model.extend({
});
var MObs = Backbone.Collection.extend({
model: DObj,
url: '/movies'
});
$.mockjax({
type: 'GET',
url: '/movies',
contentType: 'text/json',
status: 200,
response: function() {
this.responseText = JSON.stringify(new MObs([{ name: 'Lincoln' }, { name: 'Argo' }]));
}
});
var MView = Backbone.View.extend({
initialize: function() {
this.collection.on("sync", this.render, this);
},
render: function() {
alert("here : ");
}
});
(function($) {
var newCol = new MObs();
newMob = new MView({ collection: newCol });
newCol.fetch({
success: function () { console.log('Success'); },
error: function() { console.log('Error'); }
});
})(jQuery);
I think there are many issues with code, the issue you were specifically asking for has to do with the server response; you should see an alert with the above code. I recommend you read backbone.js documentation for others.

Backbone/Underscore uniqueId() Odd Numbers

I'm relatively new to Backbone and Underscore and have one of those questions that's not really an issue - just bugging me out of curiosity.
I built a very simple app that allows you to add and remove models within a collection and renders them in the browser. It also has the ability to console.log the collection (so I can see my collection).
Here's the weird thing: the ID's being generated are 1,3,5... and so on. Is there a reason specific to my code, or something to do with BB/US?
Here's a working Fiddle: http://jsfiddle.net/ptagp/
And the code:
App = (function(){
var AppModel = Backbone.Model.extend({
defaults: {
id: null,
item: null
}
});
var AppCollection = Backbone.Collection.extend({
model: AppModel
});
var AppView = Backbone.View.extend({
el: $('#app'),
newfield: $('#new-item'),
initialize: function(){
this.el = $(this.el);
},
events: {
'click #add-new': 'addItem',
'click .remove-item': 'removeItem',
'click #print-collection': 'printCollection'
},
template: $('#item-template').html(),
render: function(model){
var templ = _.template(this.template);
this.el.append(templ({
id: model.get('id'),
item: model.get('item')
}));
},
addItem: function(){
var NewModel = new AppModel({
id: _.uniqueId(),
item: this.newfield.val()
});
this.collection.add(NewModel);
this.render(NewModel);
},
removeItem: function(e){
var id = this.$(e.currentTarget).parent('div').data('id');
var model = this.collection.get(id);
this.collection.remove(model);
$(e.target).parent('div').remove();
},
printCollection: function(){
this.collection.each(function(model){
console.log(model.get('id')+': '+model.get('item'));
});
}
});
return {
start: function(){
new AppView({
collection: new AppCollection()
});
}
};
});
$(function(){ new App().start(); });
if you look in the backbone.js source code you'll notice that _.uniqueId is used to set a model's cid:
https://github.com/documentcloud/backbone/blob/master/backbone.js#L194
that means that every time you create a model instance, _.uniqueId() is invoked.
that's what causing it to increment twice.

Backbone.LayoutManager Delegated View Events

I've been working on a prototype Backbone application using Backbone.LayoutManager and I'm running into something I don't understand.
The scenario is that I have a form for adding "people" {firstname, lastname} to a list view, I save the model fine and the new item shows up in the list. I also have a remove function that works when after the page is refreshed, but if I try to delete the person I just created without a page refresh, the removeUser() function never gets called.
My code is below. Can someone help me out? I'm just trying to learn Backbone and if you have the answer to this question as well as any other criticisms, I'd be grateful. Thanks.
define([
// Global application context.
"app",
// Third-party libraries.
"backbone"
],
function (app, Backbone) {
var User = app.module();
User.Model = Backbone.Model.extend({
defaults : {
firstName: "",
lastName: ""
}
});
User.Collection = Backbone.Collection.extend({
model: User.Model,
cache: true,
url: "/rest/user"
});
User.Views.EmptyList = Backbone.View.extend({
template: "users/empty-list",
className: "table-data-no-content",
render: function (manage) {
return manage(this).render().then(function () {
this
.$el
.insertAfter(".table-data-header")
.hide()
.slideDown();
});
}
});
User.Views.Item = Backbone.View.extend({
template: "users/user",
tagName: "ul",
className: "table-data-row"
events: {
"click .remove": "removeUser"
},
removeUser: function () {
console.log(this.model);
this.model.destroy();
this.collection.remove(this.model);
this.$el.slideUp();
if (this.collection.length === 0) {
this.insertView(new User.Views.EmptyList).render();
}
}
});
User.Views.List = Backbone.View.extend({
initialize: function () {
this.collection.on("change", this.render, this);
},
render: function (manage) {
if (this.collection.length > 0) {
jQuery(".table-data-no-content").slideUp("fast", function() {
$(this).remove();
});
this.collection.each(function(model) {
this.insertView(new User.Views.Item({
model: model,
collection: this.collection,
serialize: model.toJSON()
}));
}, this);
} else {
this.insertView(new User.Views.EmptyList());
}
// You still must return this view to render, works identical to
// existing functionality.
return manage(this).render();
}
});
User.Views.AddUser = Backbone.View.extend({
template: "users/add-user",
events: {
"click input#saveUser": "saveUser"
},
render: function (manage) {
return manage(this).render().then(function () {
$("input[type='text']")
.clearField()
.eq(0)
.focus();
});
},
saveUser: function () {
var user = new User.Model({
firstName: $(".first-name").val(),
lastName: $(".last-name").val()
});
this.collection.create(user);
this
.$("input[type='text']")
.val("")
.clearField("refresh")
.removeAttr("style")
.eq(0)
.focus();
}
});
return User;
});
The problem turned out to be an incorrect response from the server. Once the server sent back the correct JSON object, everything worked correctly.

Calling view function from another view - Backbone

I have the following views in my application. Basically I want to call show_house() in App.MapView when the li of the App.HouseListElemView is clicked.
What would be the best way of doing this?
App.HouseListElemView = Backbone.View.extend({
tagName: 'li',
events: {
'click': function() {
// call show_house in App.MapView
}
},
initialize: function() {
this.template = _.template($('#house-list-template').html());
this.render();
},
render: function() {
var html = this.template({model: this.model.toJSON()});
$(this.el).append(html);
},
});
App.MapView = Backbone.View.extend({
el: '.map',
events: {
'list_house_click': 'show_house',
},
initialize: function() {
this.map = new GMaps({
div: this.el,
lat: -12.043333,
lng: -77.028333,
});
App.houseCollection.bind('reset', this.populate_markers, this);
},
populate_markers: function(collection) {
_.each(collection.models, function(house) {
var html = 'hello'
this.map.addMarker({
lat: house.attributes.lat,
lng: house.attributes.lng,
infoWindow: {
content: html,
}
});
}, this);
},
show_house: function() {
console.log('show house');
}
});
The current house is really part of your application's global state so create a new model to hold your global application state:
var AppState = Backbone.Model.extend({ /* maybe something in here, maybe not */ });
var app_state = new AppState;
Then your HouseListElemView can respond to clicks by setting a value in app_state:
App.HouseListElemView = Backbone.View.extend({
//...
events: {
'click': 'set_current_house'
},
set_current_house: function() {
// Presumably this view has a model that is the house in question...
app_state.set('current_house', this.model.id);
},
//...
});
and then your MapView simply listens for 'change:current_house' events from app_state:
App.MapView = Backbone.View.extend({
//...
initialize: function() {
_.bindAll(this, 'show_house');
app_state.on('change:current_house', this.show_house);
},
show_house: function(m) {
// 'm' is actually 'app_state' here so...
console.log('Current house is now ', m.get('current_house'));
},
//...
});
Demo: http://jsfiddle.net/ambiguous/sXFLC/1/
You might want current_house to be an actual model rather than simply the id of course but that's easy.
You'll probably be able to find all sorts of other uses for app_state once you have it. You can even add a little bit of REST and AJAX and get persistence for your application settings pretty much for free.
Events are the usual solution to every problem in Backbone and you can make models for anything you want, you can even make temporary models strictly for gluing things together.

Resources