Search code examples
backbone.jsbackbone-viewsbackbone-eventsbackbone-routing

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

Solution

  • 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.