使用KnockoutJS实现渐进增强

19

假设我们有以下数据

var data = {
  facets: [{
    name : "some name",
    values: [{
      value: "some value" 
    }]
  }]
};
我们可以轻松地将此表示为一个绑定到 knockout 模板的视图模型,如下所示:
<ul data-bind="foreach: facets">    
  <li>      
    <span data-bind="text: name"></span>
    <ul data-bind="foreach: values">            
      <li data-bind="text: value"></li>     
    </ul>
  </li>
</ul>
问题是,如何在使用渐进增强时实现相同的结果?也就是说,最初在服务器端呈现模板,然后将knockout模板和视图模型绑定到该呈现上。一个简单的服务器端模板可能长这样:
<ul>    
  <li>      
    <span>some name</span>
    <ul>            
      <li>some value</li>       
    </ul>
  </li>
</ul>

我探究了几种不同的可能性:

  • 一种方法是创建一个Knockout模板和一个服务器端模板,并通过解析服务器端模板的DOM动态生成Knockout视图模型。这样,只有在启用JavaScript时才会显示Knockout模板,而如果禁用JavaScript,则只会显示服务器端模板。它们可以以相同的方式进行样式设置,使它们看起来完全相同。

  • 另一种方法是将每个facets数组中的项分别应用绑定到该facet的现有DOM元素上。但是,这仍然只有一层深度,不适用于嵌套元素。

这些方法似乎都不太干净。另一个解决方案可以是编写一个自定义绑定,处理整个渲染过程并在可能的情况下重用现有元素。

还有其他的想法吗?


我已经放弃了使用Knockout进行渐进增强。除了一些非常简单的情况,保持非KO/KO行为同步实际上是不切实际的..这是标准JavaScript PE所面临的问题,但KO的丰富绑定模型使其在方法上存在极端分歧。(也许有一个“服务器端KO”项目?那将是..至少很有趣。) - user2864740
5个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
4

我在这里探索了几种方法,包括从第一个元素生成匿名模板,如下所述:

http://groups.google.com/group/knockoutjs/browse_thread/thread/3896a640583763d7

或通过自定义绑定为数组的每个元素创建单独的绑定,例如

ko.bindingHandlers.prerenderedArray = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        var binding = valueAccessor();              
        binding.firstTime = true;

        $(element).children().each(function(index, node) {                  
            var value = ko.utils.unwrapObservable(binding.value)[index];                        
            ko.applyBindings(value, node);
        }); 

        return { 'controlsDescendantBindings': true };              
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {             
        var binding = valueAccessor();
        if (binding.firstTime) {
            binding.firstTime = false;
            return;
        }               

        $(element).children().remove(); 
        ko.applyBindingsToNode(element, { template: { name: binding.template, foreach: binding.value }}, viewModel)
    }
};      
这里讲的是针对每个元素应用特定绑定,然后在第一次更新时使用普通的foreach绑定替换内容的方法。这种方法仍然需要两个模板。这两种方法都需要通过服务器呈现的JSON来初始化状态。 我选择使用当前方法(尽管可能会更改),即将所有Knockout模板放置在script标签中,以便在NoJS浏览器中永远不会呈现。 NoJS模板作为服务器端div的内容呈现。只要Knockout模板被呈现,div的内容就会被script标签中的Knockout模板替换。您可以以相同/类似的方式对其进行样式设置,使过渡无缝,并且如果不可能,则通过CSS隐藏noJS模板。 最终,我得出结论:Knockout.js和渐进增强并不真正很好地配合使用,应该选择其中之一,即使用更传统的方法(如直接使用jQuery DOM操作)构建需要渐进增强的应用程序的某些部分。

+1 我得出了同样的结论 - 如果没有完整的服务器端支持KO(一个新项目,有人吗?),它与传统的HTML/postback模型相比就太不一致了,实际上很难保持同步。 - user2864740

3

只需要在服务器端模板中添加各种data-属性即可。如果JavaScript被禁用,它们不会造成任何影响,因此拥有它们并不是问题。


3
只有在您拥有扁平化的数据结构时,此方法才有效。那么对于数组和嵌套的数组,您该如何处理? - Can Gencer
是的,还有向表中添加和删除行等操作。 - Rob Grant

3

恐怕没有一种干净的方法来完成这个问题。个人认为最好在后端渲染页面,然后将相同的上下文数据(序列化为JSON)传递给前端,并使用它设置Knockout。这意味着有些重复。也许将后端切换到node.js会简化此处的事情。


1
我也得出了同样的结论,不幸的是。也许在 Knockout 的未来版本中会有所改进(即通过某种方式绑定到现有的 DOM 元素)。 - Can Gencer
Tomasz,我很好奇你是如何渲染服务器端的 - 你使用的是node?rhino?还是其他什么?总的来说,我很好奇你是否已经成功地在服务器端使用了与客户端相同的模板,或者你是否需要维护两套模板。 - Karl Rosaen
@Karl:后端是Django,所以模板看起来像这样:<div data-bind="text: my_data">{{ my_data }}</div> - Tomasz Zieliński
明白了,谢谢。我在考虑尝试使用Rhino,不过绑定KO代码可能会涉及到很多DOM。 - Karl Rosaen

1
这是我的看法,这个基本示例使用自定义绑定将服务器端的值加载到视图模型中。

使用KnockoutJS实现渐进增强的无限滚动

screenshot

渐进增强绑定
ko.bindingHandlers.PE = {
    init: function(element, valueAccessor, allBindings) {
        var bindings = allBindings();
        if (typeof bindings.text === 'function') {
            bindings.text($(element).text());
        }
    }
};

0

以下是一个示例,演示如何在服务器上循环一堆项目,然后在Knockout foreach循环中重新绘制它们:http://www.tysoncadenhead.com/blog/using-knockout-for-progressive-enhancement-on-lists - Tyson Cadenhead

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