Backbone和Marionette中的额外包装器

20

使用Backbone和Marionette,我创建了一个新的布局,它放在我的页面的主要内容div中。布局如下:

<div id='dash-sidebar'>
    <div id='dash-profile'></div>
    <div id='dash-nav'></div> 
</div>
<div id='dash-content'></div>
问题在于当我渲染布局时,Backbone会自动将其包装在一个div中,然后再将其放入主内容div中,就像这样:

<div id='main-content'>    
  <div>
    <div id='dash-sidebar'>
      <div id='dash-profile'></div>
       <div id='dash-nav'></div> 
    </div>
    <div id='dash-content'></div>
  </div>
</div>
我知道可以使用“tagName”更改元素,但是否有可能避免完全包装模板,直接将其插入页面上的主内容div中?
4个回答

13

每个Backbone视图必须由一个单独的元素表示。您的第一个HTML块有两个元素,这就是为什么它不能被视图表示而需要先将其包装在外部div中的原因。

你能重构你的布局以包括main-content区域吗?然后布局的el会对应整个外部div。

另一个尝试的方法是使用Backbone.View的setElement() 方法来覆盖外部div的创建,并手动注入您想要的元素的HTML代码。类似于:

onRender: function() {
    this.setElement( /* the HTML you want for your layout */ );
}

然而,如果传入的HTML有两个父元素而不仅仅是一个,我不确定这将如何工作。


1
有没有使用木偶特定的方法来完成这个?最好我认为只使用“模板”属性会很好。 - streetlight
太好了。我真的不喜欢看到其他人的答案,他们说要操作DOM来解决这个问题。为什么要这样做呢?你只需要使用setElement()就可以了。 - trusktr

10

编辑3:警告!此答案可能已过时。我收到一条评论称,此答案不再起作用,并且没有时间进行调查(我个人不使用此方法)。

我喜欢使用Twitter/Bootstrap作为我的UI库,但由于默认标签包装(特别是在我的<tbody><tr>之间有一个<div>),我的表格样式出现了一些问题。

经过一番搜索,我在Marionette文档的Region章节找到了有关View如何附加el的内容。Backbone从各种View属性中构建el并保持构建出的元素始终更新,以便随时呈现。因此,我想,如果view.el是父级,为什么不直接使用HTML内容呢? Marionette还提供了一种创建自定义Region的方式

我通过创建一个自定义的Region并重写open函数来使一切正常运行。这样我就可以指定哪些区域要用标签包装,哪些不需要。在下面的示例片段中,我创建了我的自定义非包装RegionNowrapRegion),并在我的LayoutMyLayout)中使用它来处理<tbody>(我在实际程序中传递的视图创建<tr>)。
var NowrapRegion = Marionette.Region.extend({
  open: function(view){
    this.$el.html(view.$el.html());
  }
});

var MyLayout = Marionette.Layout.extend({
  template: templates.mylayout,
  regions: {
    foo: '#foo',
    columns: '#columns', //thead
    rows: { selector: '#rows', regionType: NowrapRegion } //tbody
  }
});

BOOM! 我需要时包装,不需要时不包装。

编辑:这似乎影响应用于*View级别的events。我还没有研究过,但请注意它们似乎没有被触发。

无论这是否是“正确”的方法,我不确定。如果@derick-bailey看到这个消息,我将欢迎任何反馈。

编辑2:@chris-neale建议以下操作,我尚未验证,但似乎很合理。谢谢!

而不是复制视图中的html,在子节点上使用深克隆将维护事件和数据。

var NowrapRegion = Marionette.Region.extend({
  open: function(view){
    view.$el.children().clone(true).appendTo(this.$el);
  }
});

2
这不应该是Marionette的一部分吗?我的意思是ItemViews应该重用仅用于选择区域的div。 - Evgeni Petrov
我并不反对,这确实会让我的观点更加简洁明了。最终,我只是随波逐流,将我的HTML模板结构化以适应默认行为。这并不太糟糕。 - clayzermk1
这个解决方案有漏洞,应该避免使用,危险!让我浪费了1个小时。 - user1972450
@eguneys 你指的是哪个解决方案?原始版本(我知道那个有缺陷 - 参见我的第一个编辑)还是由@chris-neale建议的版本?另外,你遇到了什么样的错误?我已经很久没有碰过这个了,我相信自从我写这个以来,Marionette已经改变了很多。 - clayzermk1
我尝试了两种方法,但是this.$el没有被正确地复制。DOM上下文未定义。 - user1972450

6

更新:

Marionette.Layout是Backbone视图的扩展,因此这是正常行为,请参阅Backbone文档:

"el"属性引用在浏览器中创建的DOM对象。 每个Backbone.js视图都有一个"el"属性,如果未定义,则Backbone.js将构造自己的属性,即一个空的div元素。

所以我的上一个答案与你的问题无关,对不起。

更新:

在Backbone gitHub上发现了一个与此主题相关的问题issue 546(被关闭),jashkenas发布了这个评论来解释为什么很难实现:

使用Backbone Views的一个重要优势是它们的元素始终可用 - 无论是否已渲染模板(许多视图具有多个模板) - 无论视图是否存在于DOM中。

这使您可以创建并将视图添加到DOM中,稍后进行渲染,并确保所有事件都正确绑定(因为它们是从view.el委托的)。

如果您能想出一种好的策略,允许视图具有“完整”的模板并保留上述特性......那么让我们谈谈它 - 但它必须允许根元素存在而不必渲染模板的内容(可能取决于稍后可能不会到达的数据),并且它必须允许视图轻松拥有多个模板。


1
无论使用哪种方法都不行。将ID设置为与父元素匹配只会将相同的ID添加到包装器div中,因此您将拥有两个具有相同ID的div。第二种方法无法呈现内容。我尝试了$('#main-content')和'#main-content',但都没有成功。还有其他想法吗? - Will Hitchcock
也许你可以看一下区域,参考这篇帖子 - marcoo

1
我想实现基本相同的事情。我希望在我的模板中拥有所有的HTML标记,然后让Backbone处理其余部分。因此,我编写了以下代码片段来删除额外的“div”。
首先,您将tagName设置为“detect”,然后在第一次渲染后检测视图内第一个元素的tagName。显然,这仅在您在模板中提供自己的包装器时才起作用。
// Single table row inside tbody
tableRowView = Backbone.Marionette.ItemView.extend({
  tagName: 'detect',
  template: function(data) {
    return Handlebars.templates['tablerow/single'](data);
  },
  onRender: function() {
    if (this.tagName == 'detect')
      this.tagName = this.$el.children().first().prop('tagName').toLowerCase();
    this.setElement( this.$el.children( this.tagName ) );
    var $parent = this.$el.parent( this.tagName );
    if ($parent.length) {
      this.$el.detach();
      this.$el.insertAfter($parent);
      $parent.remove();
    }
  },
  modelEvents: {
    "change": "render"
  }
});

模板:

<tr>
  <td>{{artist}}</td>
  <td>{{title}}</td>
  <td>{{length}}</td>
</tr>

然而,我只是稍微尝试了一下 - 如果有人发现这种方法可能会引起任何问题(性能、内存、僵尸进程等),我非常愿意了解。

顺便说一下,这可能很容易打包成一个插件。


你还在使用它吗?有遇到任何问题吗?这个解决方案相当有趣,但我在想它的可靠性。目前为止它还能正常工作,谢谢! - alxscms
为什么不直接将tagName设置为'tr',然后从模板中删除包装的<tr></tr>呢?如果需要向行添加类或属性,只需通过Backbone.View的attributes属性添加即可。 - D_Naish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接