jQuery主干的不规则性view.render()



在什么情况下会发生这种情况:

 this.$el.append(ret);  //doesn't work
 $(this.el).append(ret); //works

我像这样实例化视图:

new allViews.UserProfile({el: '#main-content-id',collection: collections.users}),

也许我应该使用$el而不是el来实例化视图。为什么Backbone要做这样的区分,这太疯狂了。

我之前应该提到这一点,但这是我正在做的一件非标准的事情。让我恼火的一件事是Backbone不允许在视图中使用"默认值"。这是错误的——例如,你可能想要一个视图的默认集合,但是如果你在构造函数中传递了一个集合,那么它会覆盖默认集合。很合理。所以基本上我尝试手动复制Backbone中的内容。Model for Backbone.View.

这就是视图,完全供你欣赏:

    var UserProfileView = Backbone.View.extend({
            defaults: function () {
                return {
                    model: null,
                    collection: collections.users,
                    childViews: {}
                }
            },
            events: {
            },
            constructor: function () {
                this.givenName = '@UserProfileView';
                Backbone.View.apply(this, arguments);
            },
            initialize: function (opts) {
                Backbone.setViewProps(this, opts); //has side effects
            },
            render: function () {
               var data = this.collection.models;
               renderThis.bind(this)(UserProfileView.template);

                function renderThis($template) {
                    var ret = EJS.render($template, {
                        users: data
                    });
                    //this.$el.append(ret);
                    $(this.el).append(ret);
                }
                return this;
            }
        },
        { //class properties
            template: template
        }
    );

这是每个视图都要经过的非标准函数,这个函数在view.initialize()函数中调用:

  Backbone.setViewProps = function(view,options){
            var opts = options || {};
            var temp = _.defaults({}, opts, _.result(view, 'defaults'));
            for(var prop in temp){
                if(temp.hasOwnProperty(prop) && prop !== undefined){
                    if(temp[prop]!==undefined){
                        view[prop] = temp[prop];
                    }
                }
            }
        };

所以我认为发生的事情是,当我传递el作为一个选项时,它被预先成熟地附加到视图上,然后Backbone过早地调用

 this.setElement(_.result(this, 'el'));

在主干源

 _ensureElement: function() {
      if (!this.el) {
        var attrs = _.extend({}, _.result(this, 'attributes'));
        if (this.id) attrs.id = _.result(this, 'id');
        if (this.className) attrs['class'] = _.result(this, 'className');
        this.setElement(this._createElement(_.result(this, 'tagName')));
        this._setAttributes(attrs);
      } else {
        this.setElement(_.result(this, 'el'));
      }
    },

调用

 _setElement: function(el) {
      this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
      this.el = this.$el[0];
    },

我完全不知道你在做什么,但我会在评论中回答你的问题:

我认为别人能帮助我的最好方式就是帮助我创造一个更好的生活为视图设置默认属性的方法

惹恼我的一件事是Backbone不允许

我不明白是什么阻止了你在骨干视图中定义默认值,并允许使用可以传入的options参数重写:

var View = Backbone.View.extend({
    initialize: function(options){
        this.defaults = { 
            name: "default", 
            color: "blue" 
        }; 
        this.attributes= $.extend({}, this.defaults, options); 
    }
}) 

您可以通过在实例化视图时传入这些属性来重写它们:

var view = new View({name: "Alex", color: "red"}); 
http://jsfiddle.net/C9wew/7299/

您的setViewProps函数可能是您的问题的根源。在创建视图时,将el传递到视图中。这意味着setViewProps看到的options将包含el,特别是el将是字符串'#main-content-id'。那么你的setViewProps将有效地说:

view.el = '#main-content-id`;

然后一切都崩溃了。el属性是一个DOM节点:

el view.el

所有的视图在任何时候都有一个DOM元素(el属性),[…].

this.el可以从DOM选择器字符串或元素中解析;[…].

el可以从选择器字符串解析,但它仍然应该是一个DOM元素。el 选项可以是选择器字符串,但el 属性应该是DOM元素。

注意文档中说:

所有视图在任何时候都有一个DOM元素

所以传递给视图的字符串el选项到DOM元素el属性的转换发生在 initialize被调用之前。这意味着您的setViewProps无意中用字符串覆盖el属性(一个DOM元素)。

您不应该盲目地假设传递给视图的所有选项都应该复制到视图属性中:有些属性(例如el)需要特殊处理,有些属性根本不应该复制(例如意外出现的render选项)。如果你真的必须这样做,那么你应该把要复制的属性列入白名单:

var temp = _.result(view, 'defaults') || { };
for(var prop in temp) {
    //...
}

您也可以将此函数直接附加到Backbone。视图的原型:

Backbone.View.prototype.setViewProps = function(options) {
    // use `this` instead of `view` in here
};

,然后说:

this.setViewProps(options);

在你的initialize .


这里还发生了一些奇怪的事情。

不要这样做:

constructor: function () {
    this.givenName = '@UserProfileView';
    Backbone.View.apply(this, arguments);
}

constructor是AFAIK更多关于支持CoffeeScript的class V extends Backbone.View比你应该做的任何事情。Backbone中"真正的"构造函数是initialize, Backbone将在适当的时候调用它。如果你想要一个givenName属性,那就把它和其他与实例无关的东西一起留在原型上:

var UserProfileView = Backbone.View.extend({
    givenName: '@UserProfileView',
    //...
});

或者,如果您出于某种原因需要hasOwnProperty表示true,则在initialize中分配它:

initialize: function() {
    this.givenName = '@UserProfileView';
    //...
}

在视图实例上使用for...in应该是非常罕见的,所以我将它留在原型上。

同样,将template附加到"类"上有点奇怪,您应该将其留在原型上并将其称为this.template

你的render有点奇怪:

render: function () {
    var data = this.collection.models;
    renderThis.bind(this)(UserProfileView.template);
    function renderThis($template) {
        var ret = EJS.render($template, {
            users: data
        });
        $(this.el).append(ret);
    }
    return this;
}
  1. 一般不要乱动集合的models属性。你通常使用混合下划线函数之一来迭代集合(例如this.collection.each(function(model) { ... }))或使用toJSON将其序列化为原始数据。在视图中使用toJSON更常见,这样你的模板就不会意外改变任何东西。
  2. UserProfileView.template包含在上面。
  3. $(this.el)被覆盖在上面。
  4. 我不明白为什么你有一个renderThis内部函数,为什么不在render里面做呢?

这样更习惯:

render: function() {
    var html = EJS.render(this.template, {
        data: this.collection.toJSON()
    });
    this.$el.html(html);
    return this;
}

你需要修改模板来处理一个纯对象数组,而不是模型数组。

最新更新