【问题标题】:async template loading and post-render actions with Backbone使用 Backbone 的异步模板加载和渲染后操作
【发布时间】:2012-05-01 14:17:32
【问题描述】:

我正在使用主干样板来呈现我的模板,它的 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() 是“无效的”。

当然,我可以在 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);
  });
},

【问题讨论】:

  • 可以添加fetchTemplate函数的代码吗?
  • 为什么视图作用于其范围之外的元素?您的视图中不应该有$("#some-id"),视图应该作用的所有元素都应该在this.elthis.el 下。另外,你不能只做$('#content').html(view.el); view.render();吗?

标签: javascript jquery backbone.js backbone-boilerplate


【解决方案1】:

所有这些并发症都没有必要。

原来的fetchTemplate 返回一个jQuery promise。如果您不了解 jQuery 的 Deferreds 和 Promises,那么您的版本也应该如此,现在是查看它们的好时机。回调已死;)

使用 Promise,一切都变得如此简单: 在您的initialize 中获取模板并分配承诺。然后仅在 promise 已实现时才渲染,例如:

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(...) 中。

【讨论】:

  • 哇,这是我绝对应该实现的东西,我希望它也能解决我最初的问题
  • “原来的fetchTemplate”是什么意思?我上面的代码返回呈现的模板,而不是承诺,在阅读 jquery 文档后,在我看来,承诺是“dom 级别”jquery 对象 ($("#myid").promise()) 的一部分,而不是 jQuery 本身 ($.promise)。
  • 每个异步操作(例如 jQuery.get)都会返回 Promise 原始 fetchTemplate (github.com/tbranyen/backbone-boilerplate/blob/master/app/…) 会执行此操作,您的实际操作也是如此,因为您返回 $.get 的结果,但仅当它被提取了(不是在它已经被提取的时候!更全面的解释参见addyosmani.com/blog/digging-into-deferreds-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() 并且不是来自viewHandler('selector').showView,因为我们在其中调用 onAddToDom。但无论如何,这不是必需的,因为如果我们想在渲染后调用某些东西,我们可以将它添加到 render() 方法本身。我只是想确保没有任何混淆,所以给出了这个警告。

【讨论】:

  • 在旁注中,如果您想要更通用的方式,您可以使用 Backbone.Events,但对于这个问题,我觉得这有点过头了。
  • 而且,@Esailija 说,你不应该在你的视图中使用 $("selector") ,它会造成混乱。但是,如果它是不可避免的,我的方法,至少在我看来,就足够了。
  • 不幸的是,我正在按照您的建议做,甚至更多,因为我两次调用 postrender。一次在 myView.render 内的回调结束时,一次在 showView 结束时
  • 这可以正常工作,但是当我需要只运行一次的代码并且两次调用都可以访问有意义的 DOM 树的情况下调用 postrender 时会出现问题
【解决方案3】:

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

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')
    });
  }
});

【讨论】:

  • 只是一个问题。如果 "#duel-new" 不在 this.$el 中并且它实际上包含它怎么办?据我所知,我不太确定这是否可行 - 但无论如何,我不是专家吗? :)
  • 您说得对,如果#duel-new 不在您当前尝试渲染的视图中,则上述代码将不起作用。但是你可能会在尝试访问当前视图范围之外的 DOM 元素时遇到各种痛苦。但是,如果是这种情况,那么使用事件来通知上视图进行渲染/显示/隐藏会更有意义……如果您感觉特别冒险,甚至可以考虑使用状态机.
  • 感谢您的建议,我会重写我的代码,不幸的是,这似乎并不能解决我的问题
【解决方案4】:
  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

【讨论】:

    猜你喜欢
    • 2012-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多