使用Backbone进行异步模板加载和后渲染操作

时间:2012-05-01 14:17:33

标签: javascript jquery backbone.js backbone-boilerplate

我正在使用骨干样板来渲染我的模板,它的fetchTemplate方法会缓存渲染的模板。

我想在渲染的内容上运行一些额外的代码,比如初始化手风琴等,但是使用异步编译模板执行此操作比我想象的要复杂得多。

以下是一个例子:

Duel.Views.Home = Backbone.View.extend({
  template: "/templates/duel_home.jade",
  render: function() {
    var view = this;
    statusapp.fetchTemplate(this.template, function(tmpl) {
      $(view.el).html( tmpl({duels: view.collection.toJSON()}) );
      view.postrender();
    });
    return this;
  },
  postrender: function() {
    $('#duel-new').each(function() {
      console.log('Found something')
    });
  }
});

除了上述内容,我使用http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/

中概述的视图处理程序

我这样做

var view = Duel.Views.Home({model: mymodel})
viewHandler('#content').showView(view)

这个电话

$('#content').html(view.render().el)

但是,当模板尚未缓存时,首先调用render,然后按时调用postrender。另一方面,当模板已经被缓存时,模板立即被渲染,postrender被调用,但是view.el尚未插入到DOM中,因此$(this.el)是一个空列表,并且$('#duel-new')。each()是“void”。

当然,我可以在viewHandler的render调用之后添加postrender方法,但是这会导致同样的问题,但是在第一次调用render方法时。由于模板尚未编译,因此在其元素存在之前调用postrender,因此不能在这些不存在的元素上定义处理程序。

有关如何正确克服此问题的任何想法?例如,使用.on的简单点击事件相对简单,但是更常见的结构呢,比如$('#tabs').tabs()呢?

我的fetchTemplate函数如下:

fetchTemplate: function(path, done) {
  window.JST = window.JST || {};

  // Should be an instant synchronous way of getting the template, if it
  // exists in the JST object.
  if (JST[path]) {
    return done(JST[path]);
  }

  // Fetch it asynchronously if not available from JST
  return $.get(path, function(contents) {
    var tmpl = jade.compile(contents,{other: "locals"});
    JST[path] = tmpl;

    return done(tmpl);
  });
},

4 个答案:

答案 0 :(得分:5)

不需要所有这些并发症。

原始fetchTemplate返回jQuery promise。你的版本也应如此,如果你不了解jQuery的Deferreds和Promises,那么现在是时候看看它们了。回调已经死了;)

使用promises,一切都变得如此简单: 在initialize中,您可以获取模板并分配承诺。然后仅在履行承诺时进行渲染,例如:

Duel.Views.Home = Backbone.View.extend({
    initialize: function () {
       this.templateFetched = statusapp.fetchTemplate(this.template);

    },
    ...

    render: function () {
        var view = this;
        this.templateFetched.done(function (tmpl) {
            view.$el.html( tmpl({duels: view.collection.toJSON()}) );
            ... // All your UI extras here...
        });
    }
});

请注意,一旦履行了承诺,done将始终立即运行。 如果您在视图外修改视图$el,即将代码包装在view.templatedFetched.done(...)中,您当然可以遵循相同的模式。

答案 1 :(得分:2)

我读了你给链接的文章 - 僵尸之一。很好,就我所知,它已经包含了你的问题的答案,所需要的只是它被搜索。在阅读并多次重读您的问题后,我能想到的是您可能喜欢使用.NET方式(如Zombies文章中所述)。就是这样:

// in showView method of viewHandler
if (this.currentView) {
    this.currentView.close();
}

this.currentView = view;
this.elem.html( this.currentView.render().el );
if ( this.currentView.onAddToDom )  // THIS IS THE IMPORTANT PART.
    this.currentView.onAddToDom();

在视图中,您添加了一个'onAddToDom'方法,该方法将在您的视图添加到dom时调用。这可用于调用postrender()方法,或者您可以将postrender()重命名为'onAddToDom()'。这样,问题就解决了。怎么样?说明如下。

您可以将视图重新定义为:

Duel.Views.Home = Backbone.View.extend({
  template: "/templates/duel_home.jade",
  render: function() {
    var view = this;
    statusapp.fetchTemplate(this.template, function(tmpl) {
      $(view.el).html( tmpl({duels: view.collection.toJSON()}) );
    });
    return this;
  },
  onAddToDom: function() {
    $('#duel-new').each(function() {
      console.log('Found something')
    });
  }
});

现在当你做

之类的事情时
var view = Duel.Views.Home({model: mymodel})
viewHandler('#content').showView(view);

这被称为

$('#content').html(view.render().el)
if(view.onAddToDom)
    view.onAddToDom();

将调用(之前称为)postrender()方法。

问题解决了。

警告:但是请注意,如果调用view.render(),这将失败(即onAddToDom - 或者我们应该调用postrender()? - 不会被调用)直接而不是来自viewHandler('selector').showView,因为我们从内部调用了onAddToDom。但无论如何,这不是必需的,因为如果我们想要在渲染之后调用某些东西,我们可以将其添加到render()方法本身。我只是想确保没有任何混淆,所以给了这个警告。

答案 2 :(得分:0)

您可以尝试更改标记的行:

view.$el与创建自己的$(view.el)相同,这是Backbone 0.9.0+中的语法糖

$('#duel-new')遍历整个dom-tree,而$('#duel-new', this.$el)仅在当前视图的范围内进行检查,从而大大减少了DOM遍历所花费的时间。

虽然这可能没有必要解决你的特殊和特殊问题,但我自己没有遇到任何问题

Duel.Views.Home = Backbone.View.extend({
  template: "/templates/duel_home.jade",
  render: function() {
    var view = this;
    statusapp.fetchTemplate(this.template, function(tmpl) {
      view.$el.html( tmpl({duels: view.collection.toJSON()}) ); // change this
      view.postrender();
    });
    return this;
  },
  postrender: function() {
    $('#duel-new', this.$el).each(function() { // change this
      console.log('Found something')
    });
  }
});

答案 3 :(得分:0)

  1. 您使用的是什么版本的Backbone?

  2. 您使用的是jQuery,zepto还是其他什么?什么版本?

  3. 问题只发生在模板已经缓存且被异步检索的情况下?如果是这种情况,你能在jsfiddle中创建一个例子吗?

  4. 中出现了什么问题的浏览器?
  5.   

    因此$(this.el)是一个空列表,而$('#duel-new')。each()是“void”

    请准确定义“空列表”和“无效”的含义。除非有些事情被严重搞砸,jQuery $( this.el )应该是一个长度为1的jQuery对象。jQuery $( '#duel-new' ).each()应该是一个jQuery对象,可能长度为0。

    正如@ 20100所提到的,如果你的Backbone版本支持它,你最好使用this.$el代替$( this.el )

  6.   

    这个电话

     $('#content').html(view.render().el)
    

    jQuery.html()仅记录为接受字符串参数,因此如果使用jQuery,我认为这不是一个好主意。

  7.   

    我这样做

     var view = Duel.Views.Home({model: mymodel})
     viewHandler('#content').showView(view)
    

    这不应该是new Duel.Views.Home( { model : mymodel } )吗?否则,在构造函数中,this将为Duel.Views