Backbone 子视图事件未触发,无法访问 this.model

Backbone subview event doesn't fire, access to this.model lost

我正在使用一个主视图和一个子视图,子视图有自己的子视图,但我在子子视图上丢失了事件。

查看 SO,看起来需要一个 delegateEvents,但我不知道如何或在哪里。

此外,必须将 tbody: tbodyEl[ "0" ].outerHTML 传递给我的模板似乎真的很 hacky,但我不知道这是否与事件问题有关。

非常感谢任何帮助。

主视图:

return Backbone.View.extend({
    el: "#content",
    initialize: function () {
        this.render();
    },
    render: function () {
        var todosView = new TodosView({
            collection: projectCol
        });
    }
});

待办事项视图:

return Backbone.View.extend({
    el: "#content-body",
    template: _.template([
        '<div class="table-responsive">',
        '<table id="todo-table" class="table">',
        '<span class="caption">Top <%= project %> Tasks&nbsp;&nbsp;<a id="<%= project %>" class="projectName">(See All)</span></a>',
        '<thead>',
        '<tr>',
        '<th>Task</th>',
        '<th>Due</th>',
        '</tr>',
        '</thead>',
        '<%= tbody %>',
        '</table>',
        '</div>'
    ].join("")),
    initialize: function () {
        this.render();
    },
    render: function () {
        var projectName = this.collection.models[0].attributes.project;

        var tbodyEl = $("<tbody />");
        this.collection.each(function (item) {
            var todoView = new TodoView({
                model: item
            });
            tbodyEl.append(todoView.el);
        });
        this.$el.append(this.template({
            project: projectName,
            tbody: tbodyEl["0"].outerHTML
        }));
    }
});

待办事项:

return Backbone.View.extend({
    tagName: "tr",
    className: "todo-rec",
    template: _.template([
        "<td>",
        "<label id='task' class='edit'><%= task %></label>",
        "<input id='edited-task' class='new-edit' style='display:none;' value='<%= task %>'>",
        "</td>",
        "<td>",
        "<span id='due' class='edit'><%= due %></span>",
        "<input id='edited-due' class='new-edit' style='display:none;' value='<%= due %>'>",
        "</td>",
    ].join("")),
    events: {
        "click .edit": "editFields"
    },
    initialize: function () {
        this.render();
    },
    render: function () {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    },
    editFields: function () {
        console.log("todoView: editFields clicked"); // <-- does not fire
    }
});

10 月 25 日更新:感谢@WinterSoldier,这就是我最终得到的:代码构建元素在插入 DOM 之前完成,并且 todoView.events 有效,可以完全访问 this.model, 另外,看起来更 'Backbone-ish':

// mainView:
return Backbone.View.extend( {

    el:             "#content",

    initialize:         function(){
                    this.render();
                },

    render:         function(){
                    $( "#content-body" ).empty();

                    var section = new TodosView( { collection: projectCol } ); // should be returning a section div
                    $( "#content-body" ).append( section.el );
                }                           
} );


// todosView:
return Backbone.View.extend( {

    // used tagName so the element can be built complete before inserting into DOM
    tagName:        "div",
    className:      "table-responsive",

    // tbody changed to empty element
    template:       _.template( [
                    '<table id="todo-table" class="table">',
                        '<span class="caption">Top <%= project %> Tasks&nbsp;&nbsp;<a id="<%= project %>" class="projectName">(See All)</span></a>',
                        '<thead>',
                            '<tr>',
                                '<th>Comp.</th>',
                                '<th>Task</th>',
                                '<th>Due</th>',
                                '<th>Priority</th>',
                                '<th>Delegated To</th>',
                                '<th>Project</th>',
                                '<th>Del.</th>',
                            '</tr>',
                        '</thead>',
                        '<tbody id="tbodyContent"></tbody>',
                    '</table>' ].join( "" )
                ),

    initialize:         function(){
                    this.render();
                },

    render:         function(){
                    // new: render the template first, then append rows in #tbody
                    var projectName = this.collection.models[ 0 ].attributes.project;
                    this.$el.empty().html( this.template( {
                        project: projectName
                    } ) );

                    var this2 = this;
                    this.collection.each( function( item ){
                        var todoView = new TodoView( {model: item} );
                        this2.$el.find( "#tbodyContent" ).append( todoView.el );
                    } );
                    // now returning a <div class="table-responsive" with all rows
                    return this;
                }
    } );



// todoView:
// Note: nothing changed from original code

return Backbone.View.extend( {

    tagName:        "tr",
    className:      "todo-rec",

    template:       _.template( [
                        "<td>",
                            "<label id='task' class='edit'><%= task %></label>",
                            "<input id='edited-task' class='new-edit' style='display:none;' value='<%= task %>'>",
                        "</td>",
                        "<td>",
                            "<span id='due' class='edit'><%= due %></span>",
                            "<input id='edited-due' class='new-edit' style='display:none;' value='<%= due %>'>",
                        "</td>",
                     ].join( "" )
                ),

    events:         {
                    "click .edit":      "editFields"
                },

    initialize:         function() {
                    this.render();
                },

    render:         function() {
                    this.$el.html( this.template( this.model.toJSON() ) );
                    return this;
                },


    editFields:         function() {
                    console.log( "todoView: editFields clicked", this ); 
                }

    } );

让我列出我根据问题所做的假设:

  • 模板渲染没有问题。
  • 模板代码可以替换为纯 html 以检查 事件触发。

基于这些假设,我创建了一个包含两个模型的集合并将它们传递给 TodosView,我从中迭代这些模型以生成行视图 - 然后将其添加到 'tbody' 标签。

请在此处关注 fiddle => https://jsfiddle.net/randomfifaguy/304kffed/1/

console.log("hello");
Test = Backbone.Model.extend({});
var testModel1 = new Test({
  'task': 'taskA',
  'due': 'dueByZ'
})
var testModel2 = new Test({
  'task': 'taskB',
  'due': 'dueByY'
})
console.log(testModel1.get('task'));
console.log(testModel2.get('task'));
ModelCollection = Backbone.Collection.extend({});
var models = new ModelCollection([testModel1, testModel2]);
console.log(models);

TodosView = Backbone.View.extend({
    el: "#content-body",
    initialize: function() {
      this.render();
    },
    render: function() {    
    var templ = "<div class='table-responsive'><table id='todo-table'class='table'><span class='caption'>Top Tasks&nbsp;&nbsp;<a id='<%= project %>' class='projectName'>(See All)</span></a><thead><tr><th>Task</th><th>Due</th></tr></thead><tbody></tbody></table></div>";
    $(this.el).html(templ);
    _.each(this.collection.models, function(mdl){
     var view = new TodoView({
      model: mdl
    });    
    $('tbody').append(view.el);
    });    
  }
});

MainView = Backbone.View.extend({

    el: "#content",

    initialize: function() {
      this.render();
    },

    render: function() {
      new TodosView({
        collection: models
      });
    }
});

TodoView = Backbone.View.extend({
    tagName: "tr",
    className: "todo-rec",
    events: {
      "click .edit": "editFields"
    },
    initialize: function() {
      this.render();
      this.updateContent();
    },
    render: function() {
    var html = "<td><label id='task'class='edit tasklabel'></label><input id='edited-task'class='new-edit taskinput'style='display:none;' value=''></td><td><span id='due' class='edit duelabel'></span><input id='edited-due' class='new-edit dueinput' style='display:none;'value=''></td>"
    this.$el.html(html);
    return this;
    },
    editFields: function() {
      console.log("todoView: editFields clicked");
    },
    updateContent: function() {
      this.$el.find('.tasklabel').text(this.model.get('task'))
      this.$el.find('.taskinput').val(this.model.get('task'))
      this.$el.find('.duelabel').text(this.model.get('due'))
      this.$el.find('.dueinput').val(this.model.get('due'))
    }
});

var mainViewObj = new MainView();
<body>
    <div id='content'>
      Here is a sample div
      <div id="content-body">
        content to be changed
      </div>
    </div>
</body>

不过我想知道你的html最外层的看法,以帮助你更好。 请将它与 fiddle 的输出 html 进行比较并告诉我。 希望这能回答你的问题。

正在详细回答您的评论 您可能想要更改这段代码

    //You might want to change this piece of code 

    var tbodyEl = $("<tbody />");
    this.collection.each(function (item) {
        var todoView = new TodoView({
             model: item
        });
        tbodyEl.append(todoView.el);
    });
    this.$el.append(this.template({
            project: projectName,
            tbody: tbodyEl["0"].outerHTML
    }));

你的 tbodyEl 在你渲染之前基本上没有指向任何东西 在附加到 tbody

之前尝试这样做

    this.$el.append(this.template({
         project: projectName,
         tbody: tbodyEl["0"].outerHTML
    }));
   // followed by

    this.collection.each(function (item) {
         var todoView = new TodoView({
              model: item
          });
          tbodyEl.append(todoView.el);
    });