使用KnockoutJS进行渐进式增强

时间:2012-01-22 12:34:28

标签: javascript knockout.js progressive-enhancement

假设我们有以下数据

var data = {
  facets: [{
    name : "some name",
    values: [{
      value: "some value" 
    }]
  }]
};

我们可以很容易地将其表示为绑定到挖空模板的视图模型,如下所示:

<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>

问题是,在使用渐进增强时,我们如何才能获得相同的结果?这是通过最初在服务器端渲染模板然后将挖空模板和视图模型绑定到该渲染。

简单的服务器端模板看起来像这样:

<ul>    
  <li>      
    <span>some name</span>
    <ul>            
      <li>some value</li>       
    </ul>
  </li>
</ul>

我已经探索了几种不同的可能性:

  • 一个是创建一个淘汰模板和一个服务器端模板,并通过解析服务器端模板的DOM来动态生成Knockout视图模型。这样,启用JavaScript时,只有Knockout模板可见,并且如果禁用JavaScript,则只有服务器端模板可见。它们的样式可以使它们看起来完全相同。

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

这些方法似乎都不太干净。另一种解决方案可能是编写一个自定义绑定来处理整个渲染并尽可能重用现有元素。

还有其他想法吗?

6 个答案:

答案 0 :(得分: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模板放在脚本标记中,以便它们永远不会在NoJS浏览器中呈现。 NoJS模板呈现为服务器端div的内容。一旦Knockout模板被渲染,div的内容将被脚本标签中的Knockout模板替换。您可以使用相同/相似的方式设置它们以使转换无缝,如果不可能,则通过CSS隐藏noJS模板。

最后,我得出的结论是Knockout.js和渐进增强功能并不能很好地协同工作,应该选择另一个,即使用更传统的方法构建需要渐进增强的应用程序的某些部分这种直接的jQuery DOM操作。

答案 1 :(得分:3)

只需在服务器端模板中添加各种data-属性即可。如果禁用JavaScript,它们不会受到伤害,因此拥有它们根本不是问题。

答案 2 :(得分:3)

我担心没有干净的办法。我个人在后端渲染页面然后将相同的上下文数据传递给前端(序列化为JSON)并使用它设置Knockout。这意味着存在一些重复。也许将后端切换到node.js会简化这里的事情。

答案 3 :(得分:1)

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

Progressive Enhancement with KnockoutJS Infinite Scroll

screenshot

渐进增强绑定

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

答案 4 :(得分:0)

here,我建议使用来自服务器的生成HTML的模板绑定。它的用法类似于原始的模板绑定,除了绑定将使用的少数数据属性。

答案 5 :(得分:0)

使用非循环数据(如表单)可以更轻松地进行渐进式增强(正如我在此处所写的那样:http://www.tysoncadenhead.com/blog/using-knockout-for-progressive-enhancement)。就个人而言,我认为在DOM中循环多个项目并重新渲染它们似乎是一个艰难的实现,但很难想到更好的东西。