Daniel
Daniel

Reputation: 237

Backbone.js: How to handle selection of a single view only?

I am stuck on the following issue:

I have a model with a property that defines if it is visibly selected or not, which I will call SelectModel for the purpose of this question.

SelectModel = Backbone.Model.extend({
defaults:{
 isSelected: false
}
})

Now the first part that I do not really get is how I should handle the selection in general. If I want to use the observer pattern, my View should listen to the change of the isSelected property. But my view also triggers this in the first place, so I would have.

SelectView = Backbone.View.extend({
initialize: function(){
this.model.bind("change:isSelected", this.toggleSelectionVisually)
},

events: {
"click" : toggleSelection
},

toggleSelection: function(){
this.model.set({"isSelected": !this.model.get("isSelected");
},

toggleSelectionVisually:(){
//some code that shows that the view is now selected
},
})

So this in itself already feels a bit absurd but I guess I just understand something wrong.

But the part which I really fail to implement without making my code horrible is handling the selection for multiple models that only one model is selected at a time.

SelectListView = Backbone.View.extend({
initialize: function(){
this.collection = new SelectList();
},

toggleSelection: function(){
????
}   
})

So who should notify whom of the selection change? Which part should trigger it and which part should listen? I am really stuck on this one. For a single View it is doable, for a collection I am sadly lost.

Upvotes: 2

Views: 1420

Answers (4)

fguillen
fguillen

Reputation: 38832

Very close to the solution suggested by @rrr but moving the logic from the View to the Collection where I think it bellows to:

SelectsCollection = Backbone.Collection.extend({
  initialize: function() {
    this.on( "change:selected", this.changeSelected );
  },

  changeSelected: function( model, val, opts ){
    if( val ){
      this.each( function( e ){
        if( e != model && e.get( "selected" ) ) e.set( "selected", false );
      });
    };
  },
});

Upvotes: 2

tkone
tkone

Reputation: 22738

I would recommend that your model not keep track of this, but rather the view.

In my mind the model has nothing to do with its display, but rather the data that you're tracking. The view should encapsulate all the info about where and how the data is displayed to the user

So I would put isSelected as an attribute on the view. Then it's trivial to write a method to toggle visibility. If you then need to explain the other views that a specific view is selected you can attach a listener $(this.el).on('other_visible', toggle_show) which you can trigger on your toggle_visibility method with $(this.el).trigger('other_visible')

Upvotes: 3

ryanlahue
ryanlahue

Reputation: 1151

I would have suggested the following simplification for your SelectView until I saw the second part of your question:

SelectView = Backbone.View.extend({
  events: {
    "click" : toggleSelection
  },

  toggleSelection: function(){
    this.model.set({"isSelected": !this.model.get("isSelected");
    //some code that shows whether the view is selected or not
  }
});

However, since the isSelected attribute is apparently mutually exclusive, can be toggled off implicitly when another one is toggled on, I think the way you have it is best for your case.

So, using your existing SelectView and, you could have a SelectListView as follows. WARNING: it iterates over your entire collection of models each time one is selected. If you will have a large number of models this will not scale well, and you'll want to cache the previously-selected model rather than iterating over the entire collection.

SelectListView = Backbone.View.extend({
  initialize: function(){
    this.collection = new SelectList();
    this.collection.bind('change:isSelected', this.toggleSelection, this);
  },

  toggleSelection: function(toggledModel){
    //A model was toggled (on or off)
    if(toggledModel.get('isSelected') {
      //A model was toggled ON, so check if a different model is already selected
      var otherSelectedModel = this.collection.find(function(model) {
        return toggledModel !== model && model.get('isSelected');
      });

      if(otherSelectedModel != null) {
        //Another model was selected, so toggle it to off
        otherSelectedModel.set({'isSelected': false});
      }
    }
  }   
});

Upvotes: 3

azethoth
azethoth

Reputation: 171

There are different ways you could do it. You could trigger an event on the collection itself and have all the SelectModel instances listen for it and update themselves accordingly. That seems a bit wasteful if you have a lot of SelectModel instances in the collection because most of them won't end up doing any work. What I would probably do is keep track of the last SelectModel in your View:

SelectListView = Backbone.View.extend({
  initialize: function(){
    this.collection = new SelectList();
    this.lastSelectedModel = null;
  },

  toggleSelection: function(){
    // populate newSelectedModel with the SelectedModel that you're toggling
    var newSelectedModel = getNewSelectedModel();

    if (!newSelectedModel.get('isSelected')) {
      // if the SelectModel isn't already selected, we're about to toggle it On
      // so we need to notify the previously selected SelectModel
      if (this.lastSelectedModel) {
        this.lastSelectedModel.set({isSelected: false});
      }
      this.lastSelectedModel = newSelectedModel;
    } else {
      // if the newSelectedModel we're about to toggle WAS already selected that means
      // nothing is selected now so clear out the lastSelectedModel
      this.lastSelectedModel = null;
    }
    newSelectedModel.set({isSelected: !newSelectedModel.get('isSelected')});
  }   
})

Upvotes: 0

Related Questions