Backbone: Setting server response from one model to another - backbone.js

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 :)

Related

Rerender when data is added to local storage Backbone.js

I want to rerender my UserList when data is added to localstorage,
because i'm writing chat for two tabs, and when in one tab I add user i want another tab to show them in list as well. UpdateUser is reposnible for fetching data from localstorage to userlist, and then when userlist changes it calls function AddAll(), but it is not working :(
<script type="text/javascript">
'use strict';
var app = {};
app.User = Backbone.Model.extend({
defaults: {
name: '',
status: ''
}
});
app.UserList = Backbone.Collection.extend({
model: app.User,
localStorage: new Store("people"),
});
app.userList = new app.UserList();
app.UserView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item-template').html()),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
},
initialize: function(){
this.model.on('change', this.render, this);
this.model.on('destroy', this.remove, this);
},
});
app.AppView = Marionette.View.extend({
el: '#chatapp',
initialize: function () {
app.userList.on('add', this.addAll, this);
app.userList.on('reset', this.addAll, this);
app.userList.on('change', this.addAll, this);
this.updateMessages();
this.createUser();
},
updateMessages:function(){
setInterval(function(){
app.userList.fetch();
}, 1000);
},
createUser: function(){
app.userList.create(this.newAttributes());
},
addOne: function(user){
var view = new app.UserView({model: user});
$('#user-list').append(view.render().el);
},
addAll: function(){
this.$('#user-list').html('');
app.userList.each(this.addOne, this);
},
newAttributes: function(){
return {
name: prompt('What is your name?', 'name'),
status: prompt('What is your status?', 'status')
}
}
});
app.appView = new app.AppView();
</script>
You haven't said what you are using for "Store" that is providing your localStorage based sync, so it is hard to provide an exact code sample. However, if you add these additional lines to your #initialize method:
var model = this;
$(window).bind('storage', function (e) {
model.fetch();
});
This will cause the model to reload from localStorage whenever localStorage changes. You could improve this by checking e.originalEvent.key and only reacting to changes in localStorage for that model. A storage event is fired on every window/tab except for the one that updated the localStorage object and caused the event, and only when a change is made to localStorage, so you don't need to worry about suppressing changes to localStorage when the model saves itself.

Backbone views events firing repeatedly in routes

I have a backbonejs application that contains a router file and some views , and also i'm using requirejs to add views to routes and add templates to views. here is my codes :
routes.js
var AppRouter = Backbone.Router.extend({
routes: {
"": "getLogin",
"login": "getLogin",
"register": "getRegister",
"forget-password": "getForgetPassword"
},
getLogin: function() {
require(['views/auth/loginView'], function(view) {
view = new this.LoginView();
});
},
getRegister: function() {
require(['views/auth/registerView'], function() {
view = new this.RegisterView();
});
},
getForgetPassword: function() {
require(['views/auth/forgetPasswordView'], function() {
view = new this.ForgetPasswordView();
});
},
});
var route = new AppRouter();
Backbone.history.start();
loginView.js
var LoginView = Backbone.View.extend({
el: '#wrapper',
initialize: function() {
NProgress.start();
this.render();
},
render: function() {
require(['text!partials/auth/login.html'], function(t) {
var json = { title: 'title', formName: 'frmLogin' };
var template = _.template(t);
$('#wrapper').html(template(json));
});
NProgress.done();
},
events: {
"click #btnLogin": "login"
},
login: function(e) {
e.preventDefault();
alert('some message');
}
});
also registerView.js and forgetPasswordView.js are similar to loginView.js.
now! when i change routes multiple times and hit #btnLogn it fires alert('some message'); function multiple times...!
Have you tried un-delegating the events in the view, on route change?
You could override the route method (annotated source) in your AppRouter and run it before each route is rendered.
route: function(route, name, callback) {
view.undelegateEvents();
return Backbone.Router.prototype.route.apply(this, arguments);
}
Note: Just an idea, not tested with your code

How can a Router talk to a View other than using a global variable?

I can not understand why in my Backbone app (Todo app) after I reload a page (CTRL+F5) a filterTodos method does not get called. When I simply click on links to filter Todos ("Active", "Completed") - it does get called.
You can see this feature in links below. No matter how many times you click Refresh in Browser - correct filtered results are displayed:
http://todomvc.com/architecture-examples/backbone/#/completed
http://todomvc.com/architecture-examples/backbone/#/active
I have a theory that it's because I am triggering a filter event from Router too early - a TodosView is not initialized yet and therefore it does not listenTo filter event yet.
But how a Router can inform a View to re-render itself (based on filter) if this View does not exist yet? Can't it be achieved via triggering some event in Router as I do? One possible option is to have a global variable app.FilterState.
Is there any other methods of communications between a Router and a non-constructed yet View?
For app.FilterState I will set its state in Router and then check it in View and call filterTodos function manually like so and it will work:
views/todos.js
render: function() {
app.Todos.each(function(todo) {
this.renderTodo(todo);
}, this);
if (app.FilterState !== 'all') { // <--- ADDED CODE
this.filterTodos(app.FilterState);
}
return this;
}
Existing source code:
routers/router.js
var app = app || {};
var Router = Backbone.Router.extend({
routes: {
'all': 'all',
'active': 'active',
'completed': 'completed'
},
all: function() {
console.log('all');
app.Todos.trigger('filter', 'all');
},
active: function() {
console.log('active');
app.Todos.trigger('filter', 'active');
},
completed: function() {
console.log('completed');
app.Todos.trigger('filter', 'completed');
}
});
app.Router = new Router();
Backbone.history.start();
views/todos.js
var app = app || {};
app.TodosView = Backbone.View.extend({
el: '#todo-list',
initialize: function(todos) {
console.log('initialize begin');
app.Todos.reset(todos);
this.listenTo(app.Todos, 'add', this.addOneTodo);
this.listenTo(app.Todos, 'filter', this.filterTodos);
this.render();
console.log('initialize end');
},
render: function() {
app.Todos.each(function(todo) {
this.renderTodo(todo);
}, this);
return this;
},
renderTodo: function(todo) {
var todoView = new app.TodoView({model: todo});
this.$el.append(todoView.render().el);
},
addOneTodo: function(todo) {
this.renderTodo(todo);
},
filterTodos: function(filterType) {
console.log('filter'); // <--- CODE DOES NOT REACH THIS LINE WHEN CALLED ON BROWSER'S REFRESH (F5)
var active = app.Todos.active();
var completed = app.Todos.completed();
if (filterType === 'active') {
// hide remaining
_.each(completed, function(todo) {
todo.trigger('hide');
});
//show active
_.each(active, function(todo) {
todo.trigger('show');
});
}
else if (filterType === 'completed') {
_.each(completed, function(todo) {
todo.trigger('show');
});
//show active
_.each(active, function(todo) {
todo.trigger('hide');
});
}
else if (filterType === 'all') {
app.Todos.each(function(todo) {
todo.trigger('show');
});
}
}
});
Have you considered using Backbone Marionette? It comes with a built in pub sub communication system built in that makes it super easy to do this. Overall it gives you a great organization/modularization of your code by utilizing the pub sub system.

backbone.js Newbie Collection

I am trying to write some backbone.js stuff to get a better understanding on where and if it fits in better for me on projects. Any way I have a site and I am loading a collection with page content.
Json data comes back with (pid,name,title,content) on my router the default is
defaultRoute: function (actions)
{
this.showInfo('food');
},
showInfo: function (id)
{
var view = new ContentView({ model: this._items.at(id) });
$(".active").removeClass("active");
$("#" + id).addClass("active");
view.render();
}
if I put a 0 in place of id in this "new ContentView({ model: this._items.at(0) })" I will get the first item in the collection and if I do this in the View:
var ContentView = Backbone.View.extend({
el: $('#content'),
render: function ()
{
this.el.empty();
$(this.el).append(this.model.attributes.content);
return this;
}
});
I get the content displayed perfectly but of course may not be the content I wanted
Is it possible to select from a collection based on name == "food"?? I dont want to have to map the content to id numbers defeats the purpose of storing in a db
Sorry if this seems like a foolish question but I have crawled all over looking and Im sure Im missing something simple
here is my full NavigationRouter code in case it helps
var NavigationRouter = Backbone.Router.extend({
_data: null,
_items: null,
_view: null,
routes: {
"p/:id": "showInfo",
"*actions": "defaultRoute"
},
initialize: function (options)
{
var _this = this;
$.ajax({
url: "page_data.php",
dataType: 'json',
data: {},
async: false,
success: function (data)
{
_this._data = data;
_this._items = new ItemCollection(data);
_this._view.render();
Backbone.history.loadUrl();
}
});
return this;
},
defaultRoute: function (actions)
{
this.showInfo('home');
},
showInfo: function (id)
{
var view = new ContentView({ model: this._items.at(id) });
$(".active").removeClass("active");
$("#l_" + id).parent().addClass("active");
view.render();
}
});
Backbone mixes in a bunch of Underscore's functions into its Collections.
So if you want to find the model in the collection where name === 'food', you can do:
var foodModel = this._items.find(function(model) {
return model.get('name') === 'food';
});
// this will set foodModel to the first model whose name is 'food'
As a side note, you don't need to call empty in your render function, which can just be:
render: function() {
$(this.el).html(this.model.get('content'));
return this;
}
jQuery's html function just replaces the content of an element with the html string you pass in.

Backbone performing a GET immediately after a POST

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.

Resources