将事件委派给Backbone中的父视图

时间:2012-05-07 16:25:47

标签: javascript model-view-controller events backbone.js delegation

我的观点TuneBook有几个类型为ClosedTune的子视图。我还为每首曲调OpenTune分别设置了完整页面浏览量。相同的事件都绑定在ClosedTuneOpenTune中,因此我设计了我的应用,以便它们都从共享的“抽象”视图Tune继承。

为了让我的应用程序更具可扩展性,我希望将每个ClosedTune的事件委派给TuneBook,但为了可维护性,我希望使用相同的处理程序(存储在Tune中的处理程序)TuneBook使用(虽然它们显然需要包含在某些功能中)。

我遇到的问题是,在TuneBook内找到正确的ClosedTune来调用处理程序。什么是构建这个的好方法,还是有其他好的解决方案将事件委托给父视图?

注意 - 不是Backbone View: Inherit and extend events from parent的副本(关于从父类继承的子项,而我问的是DOM中父项的子节点的子项)

3 个答案:

答案 0 :(得分:10)

在父视图中(也从Backbone.Events扩展),我会将onEvent绑定到DOM事件。在触发器上,它会触发一个主干事件,包括你的孩子看到的一些“id”属性(大概是某个行ID?)。

var TuneBook = Backbone.View.extend(_.extend({}, Backbone.Events, {
    events: {
        "click .tune .button": "clickHandler"
    },
    clickHandler: function (ev) {
        this.trigger('buttonClick:' + ev.some_id_attr, ev);
    },

}));

然后,子视图会自然地订阅与其有关的父视图事件。下面我在initialize传递父视图以及之前在options中使用的特殊id属性。

var ClosedTune = Backbone.View.extend({

    initialize: function (options) {
        options.parent.on('buttonClick:' + options.id, this.handler, this);
    },

    handler: function (ev) {
        ...
    },

});

您当然也可以在TuneOpenTune上设置类似的订阅者。

答案 1 :(得分:7)

以下是几种可能性。

1。集中化:在ClosedTune实例

中存储TuneBook个对象
  1. 存储ClosedTune中每个tune_book.tunes的引用。如何填充tune_book.tunes取决于您;既然您在TuneBook上提到了加法方法,那就是我在下面说明的内容。

  2. TuneBook事件处理程序中,使用类似事件目标的ClosedTune属性之类的内容作为密钥,从tune_book.tunes检索id。然后调用TuneClosedTune处理程序。

  3. http://jsfiddle.net/p5QMT/1/

    var Tune = Backbone.View.extend({
      className: "tune",
    
      click_handler: function (event) {
        event.preventDefault();
        console.log(this.id + " clicked");
      },
    
      render: function () {
        this.$el.html(
          '<a href="" class="button">' + this.id + '</a>'
        );
    
        return this;
      }
    });
    
    var ClosedTune = Tune.extend({});
    
    var OpenTune = Tune.extend({
      events: {
        "click .button" : 'click_handler'
      }
    });
    
    var TuneBook = Backbone.View.extend({
      events: {
        "click .tune .button" : 'click_handler'
      },
    
      click_handler: function (event) {
        var tune = this.options.tunes[
          $(event.target).closest(".tune").attr('id')
        ];
    
        tune.click_handler( event );
      },
    
      add_tune: function (tune) {
        this.options.tunes[tune.id] = tune;
        this.$el.append(tune.render().el);
      },
    
      render: function () {
        $("body").append(this.el);
        return this;
      }
    });
    
    var tune_book = new TuneBook({
      tunes: {}
    });
    
    [1, 2, 3].forEach(function (number) {
      tune_book.add_tune(new ClosedTune({
        id: "closed-tune-" + number
      }));
    });
    
    tune_book.render();
    
    var open_tune = new OpenTune({
      id: "open-tune-1"
    });
    
    $("body").append(open_tune.render().el);
    

    2。分散:使用jQuery.data()

    将视图对象与DOM对象相关联
    1. 创建ClosedTune时,请存储对其的引用,例如this.$el.data('view_object', this)

    2. 在事件监听器中,检索ClosedTune,例如$(event.target).data('view_object')

    3. 如果需要,您可以对ClosedTuneTuneBook)和OpenTune使用相同的处理程序。

      http://jsfiddle.net/jQZNF/1/

      var Tune = Backbone.View.extend({
        className: "tune",
      
        initialize: function (options) {
          this.$el.data('view_object', this);
        },
      
        click_handler: function (event) {
          event.preventDefault();
      
          var tune =
            $(event.target).closest(".tune").data('view_object');
      
          console.log(tune.id + " clicked");
        },
      
        render: function () {
          this.$el.html(
            '<a href="" class="button">' + this.id + '</a>'
          );
      
          return this;
        }
      });
      
      var ClosedTune = Tune.extend({
        initialize: function (options) {
          this.constructor.__super__.initialize.call(this, options);
        }
      });
      
      var OpenTune = Tune.extend({
        events: {
          "click .button" : 'click_handler'
        }
      });
      
      var TuneBook = Backbone.View.extend({
        events: {
          "click .tune .button": Tune.prototype.click_handler
        },
      
        add_tune: function (tune) {
          this.$el.append(tune.render().el);
        },
      
        render: function () {
          $("body").append(this.el);
          return this;
        }
      });
      
      var tune_book = new TuneBook({
        tunes: {}
      });
      
      [1, 2, 3].forEach(function (number) {
        tune_book.add_tune(new ClosedTune({
          id: "closed-tune-" + number
        }));
      });
      
      tune_book.render();
      
      var open_tune = new OpenTune({
        id: "open-tune-1"
      });
      
      $("body").append(open_tune.render().el);
      

      对评论的回应

        

      我考虑过选项1,但决定反对它,因为我已经在乐团中收集了一些曲调模型,并且不想要另一个我需要保持同步的对象

      我想这取决于你认为需要做什么样的家务/同步,以及为什么。

        

      (例如在TuneModel.remove()中我需要从tunebook的视图列表中删除视图...可能需要事件来执行此操作,因此仅事件解决方案开始看起来更具吸引力。)

      为什么你觉得你“需要从tunebook的视图列表中删除视图”? (我不是建议你不应该这样做,只是问为什么想要。)既然你这样做了,你认为@ ggozad的方法在这方面有何不同?

      这两种技术都在ClosedTune实例中存储TuneBook个对象。在@ ggozad的技术中,它只是隐藏在一个抽象背后,这可能使你不那么明显。

      在我的例子中,它们存储在一个普通的JS对象(tune_book.tunes)中。在@ ggozad中,它们存储在_callbacks使用的Backbone.Events结构中。

      添加ClosedTune

      1

      this.options.tunes[tune.id] = tune;
      

      2

      this.on('buttonClick:' + tune.id, tune.handler, tune);
      

      如果你想摆脱ClosedTune(假设你用tune.remove()从文档中删除它,并且你希望视图对象完全消失),使用@ ggozad的方法将留下一个孤立的引用ClosedTune中的tune_book._callbacks,除非您使用我建议的方法执行相同类型的内务管理:

      1

      delete this.options.tunes[tune.id];
      
      tune.remove();
      

      2

      this.off("buttonClick:" + tune.id);
      
      tune.remove();
      

      每个示例的第一行是可选的 - 取决于您是否要清理ClosedTune个对象。

        

      选项2或多或少是我现在所做的,但是(由于其他原因)我还将模型存储为视图上的数据属性。$ el,我不禁觉得必须要比在整个地方存储引用更好的方法。

      嗯,最终归结为你对如何构建事物的偏好。如果您希望以更集中的方式存储视图对象,则可以将它们存储在TuneBook实例中,而不是使用jQuery.data。见#1:集中化。

      您存储对ClosedTune个对象的引用的方式:使用jQuery.data,或TuneBook中的普通对象,或_callbacks中的TuneBook {{1}}。

      如果您因为理解的原因喜欢@ ggozad的方法,那就去吧,但这并不神奇。正如在这里介绍的那样,我不确定与#1中提出的更直接的版本相比,额外的抽象级别应该提供什么样的优势。如果有一些优势,请随时填写。

答案 2 :(得分:0)

我从article(@ dave-cadwallader评论)中获得了很好的解决方案。

扩展常规的主干事件对象并将其存储在引用vent中:

var vent = _.extend({}, Backbone.Events);

将其传递到父视图:

var parentView = new ParentView({vent: vent});

子视图将触发一个事件:

ChildView = Backbone.View.extend({    
  initialize: function(options){
    this.vent = options.vent;
  },

  myHandler: function(){
    this.vent.trigger("myEvent", this.model);
  }
});

父视图正在监听子事件:

ParentView = Backbone.View.extend({    
    initialize: function(options){
        this.vent = options.vent;
        this.vent.on("myEvent", this.onMyEvent);

        let childView = new ChildView({vent: this.vent});
    },

    onMyEvent: function(){
        console.log("Child event has been ");
    }
});

免责声明-注意vent对象必须注入到每个视图中,因此您将在本文中找到更好的设计模式来使用。