backbone - trying to rerender a view with the next

2019-08-16 12:36发布

问题:

I am trying to get into backbone and have the following which is an attempt at doing an image gallery. I am trying to use render with a model in a collection. I will show the first element of the collection but I would like to add support for simply rerendering with the next element but I don't know how to do this .

I have implemented next and previous on my model like the following:

arc.Item = Backbone.Model.extend({
    next: function () {
        if (this.collection) {
            return this.collection.at(this.collection.indexOf(this) + 1);
        }
    },previous: function () {
        if (this.collection) {
            return this.collection.at(this.collection.indexOf(this) - 1);
        }
    }
});

The problem here (there could be more than the one I am asking about though) is in the loadNext method. How would I get the current location in this collection and to increment it?

arc.ItemsGalleryView = Backbone.View.extend({
      el: $('#mig-container'),
    events: {'click .next-btn' : 'loadNext', 
             'click .previous-btn':'loadPrevious' },
       template:_.template($('#mig-image-tmp').text()),
      initialize: function() {
          _.bindAll( this, 'render' );
          // render the initial state
          var thisModel=this.collection.first();
          this.render(thisModel);
      },

      render: function(xModel) {  // <- is it ok to do it this way?
          var compiled=this.template(xModel.toJSON());
          this.$el.html(compiled);
          return this;
      },

      loadNext: function(){
        console.log('i want to load Next');
        this.render(this.collection.next());  // <- how would I do this
        this.render(this.collection.first().next());  // <- this works by always giving me the second.
        // I need to replace first with current

      },
      loadPrevious: function(){
        console.log('i want to load Previous');
      }

Or is there a better way to implement this?

thx in advance

edit #1

 arc.ItemsGalleryView = Backbone.View.extend({
        el: $('#mig-container'),
        events: {'click .next-btn' : 'loadNext', 'click .previous-btn':'loadPrevious' },
         template:_.template($('#mig-image-tmp').text()),
        initialize: function() {
          _.bindAll( this, 'render' );
          this.render(this.collection.first());  // <- this works correct
        },

        render: function(xModel) {
          console.log(xModel.toJSON());
          var compiled=this.template(xModel.toJSON());
          this.$el.html(compiled);
          return this;
        },
        loadNext: function(){
          console.log('i want to load next');
          this.render(this.collection.next());   // <- this doesn't seem to do anything, event is called correctly but doesn't seem to move to next element
        },

    However if I adjust to this, it will load the 3rd element of the array

loadNext: function(){
  console.log('i want to load Previous');
  this.render(this.collection.at(2));
},

How would I use this.collection.next() to get this behavior?

thx

回答1:

What it looks like you're looking for is a way to use the Collection to manipulate the next/prev stuff. What you currently have only puts it on the model. Here's a base Collection I use in my projects:

App.Collection = Backbone.Collection.extend({
  constructor: function(models, options) {
    var self = this;
    var oldInitialize = this.initialize;
    this.initialize = function() {
      self.on('reset', self.onReset);
      oldInitialize.apply(this, arguments);
    };
    Backbone.Collection.call(this, models, options);
  },
  onReset: function() {
    this.setActive(this.first());
  },
  setActive: function(active, options) {
    var cid = active;
    if ( active instanceof Backbone.Model ) {
      cid = active.cid;
    }
    this.each(function(model) {
      model.set('current', model.cid === cid, options);
    });
  },
  getActive: function() {
    return this.find(function(model) {
      return model.get('current');
    });
  },
  next: function() {
    return this.at(this.indexOf(this.getActive()) + 1);
  },
  prev: function() {
    return this.at(this.indexOf(this.getActive()) - 1);
  }
});

It's probably not perfect, but it works for me. Hopefully it can at least put you on the right track. Here is how I use it:

var someOtherCollection = App.Collection.extend({
    model: MyModel
});


回答2:

kalley's answer is right on, but I will throw in another example.

I would entertain the idea of keeping the current model inside of state model, and work from there. You can store other application information within the state model as well.

Your model declaration would look like the following. Notice I renamed previous to prev. Backbone.Model already has a previous method and we don't want to over-ride it.

var Model = Backbone.Model.extend({
  index:function() {
    return this.collection.indexOf(this);
  },
  next:function() {
    return this.collection.at(this.index()+1) || this;
  },
  prev:function() {
    return this.collection.at(this.index()-1) || this;
  }
});

Have a generic Backbone.Model that holds your selected model:

var state = new Backbone.Model();

In the view you will listen for changes to the state model and render accordingly:

var View = Backbone.View.extend({
  el: '#mig-container'
  template:_.template($('#mig-image-tmp').html()),
  events: {
    'click .prev' : 'prev',
    'click .next' : 'next'
  },
  initialize:function() {
    this.listenTo(state,'change:selected',this.render);
    state.set({selected:this.collection.at(0)});
  },
  render:function() {
    var model = state.get('selected');
    this.$el.html(this.template(model.toJSON()));
    return this;
  },
  next:function() {
    // get the current model
    var model = state.get('selected');

    /* Set state with the next model, fires a change event and triggers render.
       Unless you are at the last model, then no event will fire.
     */
    state.set({selected:model.next()});
  },
  prev:function() {
    var model = state.get('selected');
    state.set({selected:model.prev()});
  }
});

Here is a demo. I like the state model approach because I'm not storing application-state information within my models.

If you don't like the state model approach, you can always just throw it on the floor:

/ .. code above ../

initialize:function() {
  this.model = this.collection.at(0);
  this.render();
},
render:function() {
  this.$el.html(this.template(this.model.toJSON()));
  return this;
},
next:function() {
  this.model = this.model.nxt();
  this.render();
},
prev:function() {
  this.model = this.model.prev();
  this.render();
}