在Backbone.js视图中动态设置id和className

85

我正在学习和使用Backbone.js。

我有一个Item模型和相应的Item视图。 每个模型实例都有item_class和item_id属性,我希望它们在相应视图的'id'和'class'属性中得到反映。 如何正确实现这一点?

示例:

var ItemModel = Backbone.Model.extend({      
});

var item1 = new ItemModel({item_class: "nice", item_id: "id1"});
var item2 = new ItemModel({item_class: "sad", item_id: "id2"});

var ItemView = Backbone.View.extend({       
});

我应该如何实现视图,使得视图中的 'el' 能够翻译为:

<div id="id1" class="nice"></div>
<div id="id2" class="sad"> </div>

在我看过的大多数例子中,视图的 el 作为一个无意义的包装元素,需要手动编写“语义”代码。

var ItemView = Backbone.View.extend({
   tagName:  "div",   // I know it's the default...

   render: function() {
     $(this.el).html("<div id="id1" class="nice"> Some stuff </div>");
   }       
});

因此,当呈现时,人们会得到以下结果

<div> <!-- el wrapper -->
    <div id="id1" class="nice"> Some stuff </div>
</div>

但这似乎是一种浪费——为什么要有外部的div?我希望el直接转换成内部的div!

8个回答

133

概述:使用模型数据动态设置视图属性

http://jsfiddle.net/5wd0ma8b/

// View class with `attributes` method
var View = Backbone.View.extend( {
  attributes : function () {
    // Return model data
    return {
      class : this.model.get( 'item_class' ),
      id : this.model.get( 'item_id' )
    };
  }
  // attributes
} );

// Pass model to view constructor
var item = new View( {
  model : new Backbone.Model( {
    item_class : "nice",
    item_id : "id1"
  } )
} );
  • 假设你允许 Backbone 为你生成 DOM 元素,这个示例就是在这个前提下。

  • attributes 方法会在设置视图构造函数传递的属性后被调用(在本例中是 model),让你能够在 Backbone 创建 el 之前使用模型数据动态地设置属性。

  • 与其他答案相比:不在视图类中硬编码属性值,而是从模型数据中动态设置它们;不等到 render() 来设置属性值;不在每次调用 render() 时重复设置属性值;不需要在 DOM 元素上手动设置属性值。

  • 请注意,如果在调用 Backbone.View.extend 或视图构造函数(如 new Backbone.View)时设置类名,则必须使用 DOM 属性名称 className,但如果通过 attributes 哈希/方法(如本示例)来设置它,则必须使用属性名称 class

  • 自 Backbone 0.9.9 起:

    声明视图时...eltagNameidclassName 现在可以定义为函数,如果你想在运行时确定它们的值。

    我提到这点是为了以防万一有一个场景,使用一个 attributes 方法作为替代会很有用。

使用现有元素

如果你正在使用现有元素(例如将 el 传递给视图构造函数)...

var item = new View( { el : some_el } );

如果不在元素上设置期望的属性,或者您不想在视图类和其他位置复制该数据,那么这些attributes不会应用到元素上。因此您可能需要向视图构造函数添加一个initialize方法,将attributes应用于el。代码示例(使用jQuery.attr):

View.prototype.initialize = function ( options ) {
  this.$el.attr( _.result( this, 'attributes' ) );
};

使用el属性进行渲染,避免使用无意义容器

在我看过的大多数例子中,视图的el属性都作为一个无意义的容器元素,需要手动编写 '语义化' 代码。

实际上,view.el没有必要成为“无意义的包装元素”。事实上,这通常会破坏DOM结构。例如,如果一个视图类代表一个<li>元素,它需要呈现为<li> -- 将其呈现为<div>或任何其他元素都会破坏内容模型。您可能希望专注于正确设置视图元素(使用tagNameclassNameid等属性),然后再呈现其内容。

如何让您的Backbone视图对象与DOM交互的选项是非常广泛的。有两种基本的初始场景:

  • 您可以将现有的DOM元素附加到Backbone视图上。

  • 您可以让Backbone创建一个新的与文档分离的元素,然后在某种程度上将其插入文档中。

您可以通过各种方式为元素生成内容(设置字面字符串,如您的示例;使用Mustache、Handlebars等模板库)。视图中如何使用el属性取决于您正在做什么。

现有元素

您呈现的示例表明您有一个现有元素分配给视图,尽管您没有显示视图的实例化。如果是这种情况,并且元素已经存在于文档中,则可以像这样操作(更新el的内容,但不更改el本身):

render : function () {
  this.$el.html( "Some stuff" );
}

http://jsfiddle.net/vQMa2/1/

生成元素

假设您没有现有的元素,并允许Backbone为您生成一个元素。你可能想要像这样做(但更好的做法是架构设计应该使视图不必知道自身外部的任何事情):

render : function () {
  this.$el.html( "Some stuff" );
  $( "#some-container" ).append( this.el );
}

http://jsfiddle.net/vQMa2/

模板

在我的情况下,我正在使用模板,例如:

<div class="player" id="{{id}}">
<input name="name" value="{{name}}" />
<input name="score" value="{{score}}" />
</div>
<!-- .player -->

这个模板代表完整的视图。换句话说,模板周围不会有包装器 - div.player 将成为我的视图的根或最外层元素。

我的播放器类看起来会像这样(其中包含render()的非常简化的示例):

Backbone.View.extend( {
  tagName : 'div',
  className : 'player',

  attributes : function () {
    return {
      id : "player-" + this.model.cid
    };
  },
  // attributes

  render : function {
    var rendered_template = $( ... );

    // Note that since the top level element in my template (and therefore
    // in `rendered_template`) represents the same element as `this.el`, I'm
    // extracting the content of `rendered_template`'s top level element and
    // replacing the content of `this.el` with that.
    this.$el.empty().append( rendered_template.children() );
  }      
} );

用函数覆盖属性属性并再次返回对象的绝妙方法! - Kel
2
@Kel 是的,这是一种很好的方法来实现像问题所要求的动态内容,用模型数据填充属性,而不必使用重复的代码来实例化视图。你可能已经知道了,但以防万一不明显,Backbone 的一个特性是你可以使用返回哈希表的函数作为 attributes 的值,就像其他一些可以提供函数或其他类型值的 Backbone 属性一样。在这些情况下,Backbone 检查该值是否为函数,调用它并使用返回值。 - JMM

95

在您的视图中,只需像这样做

var ItemView = Backbone.View.extend({
   tagName:  "div",   // I know it's the default...

   render: function() {
     $(this.el).attr('id', 'id1').addClass('nice').html('Some Stuff'); 
   }       
});

这是这个问题的正确答案,应该被接受。 - reach4thelasers
13
这个答案没有展示根据模型数据动态设置视图属性的方法,它只是展示了另一种硬编码属性值的替代方法。 - JMM
3
@JMM - 他的样例代码也没有使用模型数据。这个答案是基于他的样例代码工作的。显然,可以用模型数据替换这些值来使其工作。 - Clint
5
@Clint,我不认为这对问问题的人很明显。“他的示例代码也没有使用模型数据。”——那是因为他不知道如何使用,所以才会请求帮助。我认为他正在询问如何使用模型数据设置view.el的属性,并且不知道如何着手解决问题。答案甚至没有展示如何做到这一点,而且为什么要等到渲染时才这样做,或者每次渲染时都要这样做呢?“这个答案有效......”——它是如何有效的?像这样创建的每个视图都将具有相同的属性值。它唯一展示的就是如何避免使用包装器。 - JMM
OP自12年2月以来就不见了。:( 这里是对这个答案的另一个+1。 - Almo

27

你可以在根元素上设置属性 classNameid:

http://documentcloud.github.com/backbone/#View-extend

var ItemView = Backbone.View.extend({
   tagName:  "div",   // I know it's the default...
   className : 'nice',
   id : 'id1',
   render: function() {
     $(this.el).html("Some stuff");
   }       
});

编辑 包括使用构造函数参数设置ID的示例。

如果视图按照提到的方式构建:

var item1 = new ItemModel({item_class: "nice", item_id: "id1"});
var item2 = new ItemModel({item_class: "sad", item_id: "id2"});

那么值可以这样设置:

// ...
className: function(){
    return this.options.item_class;
},
id: function(){
    return this.options.item_id;
}
// ...

3
我不认为这个答案是正确的,因为每个“ItemView”都将具有“id:'id1'”。这必须根据“model.id”在执行时间中计算。 - fguillen
当然,您可以按照自己的方式设置ID。使用函数、变量或任何其他方法。我的代码只是包含一个示例,指出如何在根元素上设置值。 - Jørgen
我已经添加了一个示例,阐明如何根据构造函数参数动态设置值。 - Jørgen
这是正确的答案。它正确地使用了Backbone的特性来解决问题。 - Marc-Antoine Lemieux

6
我知道这是一个老问题,但是为了参考而添加。在新版本的Backbone中似乎更容易实现。在Backbone 1.1中,id和className属性在函数ensureElement中被评估(见源代码),使用下划线_.result意味着如果classNameid是一个函数,它将被调用,否则其值将被使用。

因此,您可以直接在构造函数中给出className,给出另一个参数以在className中使用等等... 有很多选择

所以这应该可以工作

var item1 = new ItemModel({item_class: "nice", item_id: "id1"});
var item2 = new ItemModel({item_class: "sad", item_id: "id2"});

var ItemView = Backbone.View.extend({       
  id: function() { return this.model.get('item_id'); },
  className: function() { return this.model.get('item_class'); }
});

你的例子是无效的,正确的写法应该是 id: function() { return this.model.get('item_id'); } - Cobby

4
其他示例没有展示如何从模型中实际获取数据。为了从模型的数据中动态添加id和class:
var ItemView = Backbone.View.extend({
   tagName:  "div",

   render: function() {
     this.id = this.model.get('item_id');
     this.class = this.model.get('item_class');
     $(this.el).attr('id',this.id).addClass(this.class).html('Some Stuff'); 
   }       
});

这是'this.className'还是'this.class'? - Gabe Rainbow

2

您需要删除tagName并声明一个el。

'tagName' 表示您想让backbone创建一个元素。如果元素已存在于DOM中,则可以指定el,如下所示:

el: $('#emotions'),

之后:

render: function() { 
     $(this.el).append(this.model.toJSON());
}

2

尝试在初始化方法中分配值,这将直接动态地将id和class分配给div属性。

var ItemView = Backbone.View.extend( {
    tagName : "div",   
    id      : '',
    class   : '',

    initialize : function( options ) {
        if ( ! _.isUndefined( options ) ) {
            this.id = options.item_id;
            this.class= options.item_class;
        }
    },

    render : function() {
        $( this.el ).html( this.template( "stuff goes here" ) ); 
    }
} );

@Michel 请查看此文档,http://backbonejs.org/#View-constructor - Hemanth

0
这是一种最简单的方法,通过模型动态更改视图元素的类,并在模型更改时更新它。
var VMenuTabItem = Backbone.View.extend({
    tagName: 'li',
    events: {
        'click': 'onClick'
    },
    initialize: function(options) {

        // auto render on change of the class. 
        // Useful if parent view changes this model (e.g. via a collection)
        this.listenTo(this.model, 'change:active', this.render);

    },
    render: function() {

        // toggle a class only if the attribute is set.
        this.$el.toggleClass('active', Boolean(this.model.get('active')));
        this.$el.toggleClass('empty', Boolean(this.model.get('empty')));

        return this;
    },
    onClicked: function(e) {
        if (!this.model.get('empty')) {

            // optional: notify our parents of the click
            this.model.trigger('tab:click', this.model);

            // then update the model, which triggers a render.
            this.model.set({ active: true });
        }
    }
});

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