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.