Question

I'm attempting to create an app with li elements as selectable views. The app requires that multiple elements be selected using a click-and-drag interface. Because of this, I'm using Backbone.js to track the elements state and view, as well as jQuery UI Selectable for the selection UI.

Currently, the only structure I have is a main App view, a Model and View for the li elements, and a Collection for the models.

What is the best way to structure this so that my main App view can keep track of and interact with the selected elements?

Was it helpful?

Solution

Here is an abridged version of something I did quite a while ago. It might be overly complicated for what you need, but it did work. I needed to be able to programmatically select/deselect items as well as letting jQuery-selectable handle it, which is why the model has a Selected attribute. With this approach, you can do model.set({Selected:true}) and everything stays in sync in the UI.

Added jsFiddle to demonstrate it: http://jsfiddle.net/phoenecke/VKRyS/5/

Then in your App view, you can do something like:

_.each(collection.where({Selected:true}), function(item){
    // do something with item
});

Hope it helps.

ItemView:

var ItemView = Backbone.View.extend({

  tagName: "li",

  initialize: function(options) {
     this.listenTo(this.model, 'change:Selected', this.selectedChanged);
  },

  render: function() {
     this.$el.html(template(this.model));
     // add cid as view's id, so it can be found when jQuery-selectable says selection changed (in ListView below).
     this.$el.attr({
        'id': this.model.cid
     });
     this.selectedChanged();
     return this;
  },

  selectedChanged: function() {
     // respond to model's Selected change event.
     // This way, you can programmatically change it, ie. model.set({Selected:true}).
     // but usually this class is already on the view, 
     // since jQuery selectable adds/removes it. it does not hurt to re-add it,
     // but you could check for the class.
     if(this.model.get('Selected')) {
        this.$el.addClass('ui-selected');
     } else {
        this.$el.removeClass('ui-selected');
     }
  }
});

ListView:

var ListView = Backbone.View.extend({
    initialize: function(options) {
        this.listenTo(this.collection, 'reset', this.render);
        this._views = {};
    },

    render: function() {
        this.collection.each(function(item) {
            var view = new ItemView({model: item});
            this._views[item.cid] = view;
            this.$el.append(view.render().el);
        }, this);
        this.setupSelectable();
    },

    setupSelectable: function() {
        var thisView = this;
        // Set up JQuery UI Selectable
        this.$el.selectable({
            filter: 'li',
            selected: function(event, ui) {
                var cid = ui.selected.id;
                var view = thisView._views[cid];
                if(view) {
                    // I think I only did this stuff to keep the model's Selected 
                    // attribute in sync with the UI. 
                    // this will trigger a redundant call to 'selectedChanged' in the
                    // ItemView, but it has no negative effect.
                    view.model.set({
                        Selected: true
                    });
                }
            },
            unselected: function(event, ui) {
                var cid = ui.unselected.id;
                var view = thisView._views[cid];
                if(view) {
                    view.model.set({
                        Selected: false
                    });
                }
            }
        });
    }
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top