官术网_书友最值得收藏!

  • Mastering Backbone.js
  • Abiee Echamea
  • 1229字
  • 2021-07-23 14:39:34

CollectionView

Backbone Collections are composed of many models, so when rendering a collection what we need is to render a list of Views:

class CollectionView extends Backbone.View {
  render() {
    // Render a view for each model in the collection
    var html = this.collection.map(model => {
      var view = new this.modelView(model);
      view.render();
      return view.$el;
    });

    // Put the rendered items in the DOM
    this.$el.html(html);
    return this;
  }
}

Note that the modelView property should be a View class; it could be our ModelView class of the previous section or any other view. See how for each model in the collection it instantiates and renders a this.modelView with the current model. As a result, an html variable will contain an array of all rendered views. Finally the html array can be attached easily to the $el element.

For an example of how to use CollectionView, see the following example:

class MyModelView extends ModelView {
  // …
)

class MyView extends CollectionView {
  constructor(options) {
    super(options);
    this.el = '#main';
    this.modelView = MyModelView;
  }
}

var view = new MyView({collection: someCollection});
view.render();

This snippet will do the job, it will render a MyModelView for each model in the someCollection object and put the result list in the #main element.

However, if you add models to the collection or remove them, the view will not be updated. That's not a desirable behavior. When a model is added, it should add a new view at the end of the list; if a model is deleted from the collection, the view associated with that model should be deleted.

A quick and dirty way to sync collection changes and views is to re-render the entire view on every change in the collection, but this approach is very inefficient because client resources are consumed when re-rendering views that don't need to change. A better approach should exist.

Adding new models

When a model is added to the collection an add event is triggered; we can create an event handler to update the view:

class CollectionView extends Backbone.View {
  initialize() {
    this.listenTo(this.collection, 'add', this.addModel);
  }

  // ...
}

When the addModel method is called, it should create and render a new view with the data of the model added and put it at the end of the list.

var CollectionView = Backbone.View.extend({
  // ...
  // Render a model when is added to the collection
  modelAdded(model) {
    var view = this.renderModel(model);
    this.$el.append(view.$el);
  }

  render() {
    // Render a view for each model in the collection
    var html = this.collection.map(model => {
      var view = this.renderModel(model);
      return view.$el;
    });

    // Put the rendered items in the DOM
    this.$el.html(html);
    return this;
  }

  renderModel(model) {
    // Create a new view instance, modelView should be
    // redefined as a subclass of Backbone.View
    var view = new this.modelView({model: model});

    // Keep track of which view belongs to a model
    this.children[model.cid] = view;

    // Re-trigger all events in the children views, so that
    // you can listen events of the children views from the
    // collection view
    this.listenTo(view, 'all', eventName => {
      this.trigger('item:' + eventName, view, model);
    });

    view.render();
    return view;
  }
}

A renderModel() method was added since both methods, render() and modelAdded(), need to render the model in the same way. The DRY principle was applied.

When a child view is rendered, it is useful to listen for all the events for the given view, so that we can listen for child events from the collection.

var myCollectionView = new CollectionView({...});

myCollectionView.on('item:does:something', (view, model) => {
  // Do something with the model or the view
});

Our event handler is very simple; it renders the added model with the renderModel() method, attaches an event handler for any event in the view, and appends the result at the end of the DOM element.

Deleting models

When a model is removed from the collection, the view that contains that model should be deleted from the DOM to reflect the current state of the collection. Consider an event handler for the removed event:

function modelRemoved(model) {
  var view = getViewForModel(model); // Find view for this model
  view.destroy();
}

How can we obtain the view associated with the model? There is no easy way to do it with the code that we have. To make it easy, we can keep track of model-view associations; in this way, getting the view is very easy:

class CollectionView extends Backbone.View {
  initialize() {
    this.children = {};
    this.listenTo(this.collection, 'add', this.modelAdded);
    this.listenTo(this.collection, 'remove', this.modelRemoved);
  }

  // ...

  // Close view of model when is removed from the collection
  modelRemoved(model) {
    var view = this.children[model.cid];

    if (view) {
      view.remove();
      this.children[model.cid] = undefined;
    }
  }

  // ...

  renderModel(model) {
    // Create a new view instance, modelView should be
    // redefined as a subclass of Backbone.View
    var view = new this.modelView({model: model});

    // Keep track of which view belongs to a model
    this.children[model.cid] = view;

    // Re-trigger all events in the children views, so that
    // you can listen events of the children views from the
    // collection view
    this.listenTo(view, 'all', eventName => {
      this.trigger('item:' + eventName, view, model);
    });

    view.render();
    return view;
  }
}

At rendering time, we store a reference to the view in the this.children hash table for future reference, since render() and modelAdded() use the same method to render; this change is done in one place, the renderModel() method.

When a model is removed, the modelRemoved() method can easily find the view and remove it by calling the standard remove() method and destroying the reference in the this.children hash.

Destroying views

When a CollectionView is destroyed, it should remove all children views to clean the memory properly. This should be done by extending the remove() method:

class CollectionView extends Backbone.View {
  // ...
 
  // Close view of model when is removed from the collection
  modelRemoved(model) {
    if (!model) return;

    var view = this.children[model.cid];
    this.closeChildView(view);
  }

  // ...

  // Called to close the collection view, should close
  // itself and all the live childrens
  remove() {
    Backbone.View.prototype.remove.call(this);
    this.closeChildren();
  }

  // Close all the live childrens
  closeChildren() {
    var children = this.children || {};

    // Use the arrow function to bind correctly the "this" object
    _.each(children, child => this.closeChildView(child));
  }

  closeChildView(view) {
    // Ignore if view is not valid
    if (!view) return;

    // Call the remove function only if available
    if (_.isFunction(view.remove)) {
      view.remove();
    }

    // Remove event handlers for the view
    this.stopListening(view);

    // Stop tracking the model-view relationship for the
    // closed view
    if (view.model) {
      this.children[view.model.cid] = undefined;
    }
  }
}

Now, when the view needs to be removed, it will do it and clean all the children views.

Resetting the collection

When a collection is wiped, the view should re-render the entire collection, because all items were replaced:

class CollectionView extends Backbone.View {
  initialize() {
    // ...
    this.listenTo(this.collection, 'reset', this.render);
  }

  // ...
}

This works, but previous views should be closed too; as we saw in the previous section, the best place to do it is in the render method:

class CollectionView extends Backbone.View.extend({
  // ...
  render () {
    // Clean up any previous elements rendered
    this.closeChildren();

    // Render a view for each model in the collection
    var html = this.collection.map(model => {
      var view = this.renderModel(model);
      return view.$el;
    });

    // Put the rendered items in the DOM
    this.$el.html(html);
    return this;
  }

  // ...
}

If a view has no items yet, the closeChildren() method will not do anything.

主站蜘蛛池模板: 西安市| 乌鲁木齐县| 兴国县| 中西区| 泸水县| 刚察县| 昭苏县| 巧家县| 高雄市| 新绛县| 长春市| 全椒县| 大连市| 西宁市| 定襄县| 安远县| 东至县| 长丰县| 厦门市| 新和县| 江口县| 哈密市| 乾安县| 东明县| 西青区| 白河县| 鸡泽县| 南京市| 南靖县| 辽源市| 景泰县| 福鼎市| 军事| 苏尼特左旗| 高平市| 吉水县| 松桃| 南川市| 海城市| 垫江县| 克什克腾旗|