【问题标题】:knockout: callback function when the content of a template changesknockout:模板内容发生变化时的回调函数
【发布时间】:2017-05-26 23:10:58
【问题描述】:

我希望在模板内容发生变化时捕捉事件;也就是说,模板每次重新渲染之后。

例子:

<script type="text/html" id="myTemplate">
<span data-bind="text: myBoolean"></span>
</script>
<!-- ko template: { name: 'myTemplate', afterRender: myafterRenderCallback } -->
<!-- /ko-->

在 javascript 中,我只有:

self.myBoolean = ko.observable(true);

这个 observable 发生了变化,比如说,有一些其他按钮,点击它会改变 myBoolean 的值。

因此,每次 myBoolean 更改时,模板的内容都会更新。当模板的重新渲染完成时,我需要回调函数,这意味着它已经将值从 true 更改为 false,或者从 false 更改为 true。

我尝试了两种解决方案,但都没有奏效:

  1. afterRender: myafterRenderCallback 仅在第一次渲染模板时调用一次。每次 myBoolean 更改后都不会调用它。

  2. 对 myBoolean 使用类似监听器:

    self.myBoolean.subscribe(function() {

    //待办事项

    });

第二种方法的问题是,订阅函数被调用太快,甚至在模板更新视图中的 myBoolean 之前,这导致了这个问题,这是一个很长的故事(我可以,如果你真的想知道:比如一个简单的例子,我需要访问DOM,模板比上面那个更复杂)。

我尝试了另一种方法,使用 setTimeout,所以在 myBoolean 更改后等待 2 秒左右,但这更像是一个肮脏的解决方案。

欢迎提出任何建议。

==================================

按要求提供有关模板内容的信息:

所以我在这里为您提供更多信息:我有一个淘汰组件,称为 ToolBar。它是一个模板;它只是一个带有一些 CSS 的容器。仅此而已。它包含一组按钮。一些按钮有时是可见的,有时是不可见的(我们称之为按钮的状态)。现在,每当按钮更改其状态时,我们希望残障用户能够仅使用键盘(它是 WAI-ARIA)浏览按钮,通常按 Tab 键或箭头键。

问题是,每当按钮动态更改其状态时,它都会破坏键盘的可访问性。这种损坏功能的原因是通过键盘控制导航的(无论哪种)技术都不知道按钮的新更改状态。示例:按 Tab 或箭头键只会跳过新可见的按钮,或者卡在新不可见的按钮上。或者解决这个问题,我需要在 DOM 完成重新渲染后简单地刷新工具栏(有刷新功能),以便 WAI-ARIA 知道所有按钮的最终状态,从而启用导航键盘。

因此,刷新函数需要在模板的整个重新渲染完成时调用,即当所有按钮都完成更改状态时。因此,我需要那个事件,或者那个事件的回调。

依赖 observables(上面提到的方法 2)会导致一个问题:observable 的 subscribe 函数在 DOM 更新之前被调用,因此刷新函数被称为 DOM 也被更新,因此为时过早,并且键盘只是对按钮的旧状态进行操作,这导致导航被破坏。

请注意这里没有异步进程。按钮的状态会随着选择表格的哪一行而改变,或者在一个简化的例子中,让我们假设在其他地方还有一些其他按钮,点击它们会改变工具栏中按钮的状态。

【问题讨论】:

  • 没有内置的方法来监视整个模板的更改。渲染后,您必须注意单个可观察对象的更改,这是您在第二个示例中尝试做的事情。我认为我们确实需要更多关于在这种情况下出了什么问题的信息。听起来您的模板中某处有一个异步进程需要在继续之前完成,因此您可能需要识别该进程并更改它以返回延迟对象。
  • 我刚刚阅读了您的问题。我发现很难从您概述的人为场景中解读您的实际用例。我从一开始就使用淘汰赛,而且经常当我发现自己处于这种情况时,我通常会遇到架构问题,而不是淘汰赛问题。如果您进一步解释实际实现,也许可以提供更合理的建议。当然只是我的两美分。
  • @PimBrouwers 你是对的。有时它只是一个架构问题。刚刚更新了我的帖子,添加了很长的解释。希望它能更好地澄清事情。
  • 看来我需要监听 DOM 变化。刚抬头发现MutationObserver。所以这可能与淘汰赛无关。还有比这更好的解决方案吗?
  • 对我来说,这句话突出包含答案:“问题是,每当一个按钮动态改变其状态时,它会破坏键盘的可访问性。”——对我来说,这表明你需要自定义绑定,在“更新”函数中指定您的可访问性键盘绑定。这会将可观察的状态更改(或计算,如果您正在收听多个)重新绑定到仅活动按钮。此自定义绑定可以放置在组件内的最高级别 DOM 元素上,以使其能够访问它可能需要操作的所有 HTML。

标签: javascript html templates knockout.js callback


【解决方案1】:

正如我在上面的评论中所建议的那样。我相信这句话:

问题是,每当一个按钮动态改变它的状态时,它 打破了键盘的可访问性。

包含答案。

这表明这里需要“自定义绑定”。此绑定将使用 update 函数来监听 observable 的状态变化(或者在监听多个 observable 时计算),以便重新绑定可访问性键盘绑定。

代码如下:

ko.bindingHandlers.accessibilityBindings = {
    update: function(element, valueAccessor) {
        //keyboard binding code goes here
    }
};

【讨论】:

    【解决方案2】:

    我会尝试将所有启用/禁用/更改模板的可观察对象收集到一个(可观察的)数组中。然后,您可以在延迟的ko.computed 中评估它们以触发一些更新/回调函数。

    例如:

    this.buttonStates = [ this.button1Enabled, this.button2Enabled ];
    
    ko.computed(function someButtonUpdated() {
      this.buttonStates.forEach(function(state) { state(); });
      myCallbackFunction();
    }, this).extend({ deferred: true });
    

    如果您不想跟踪视图中的状态,您可以使用自定义绑定在init 中注册(推送到buttonStates),并在节点上取消注册(从buttonStates 中删除)删除。

    <button data-bind="enable: button1Enabled, 
                       register: { 
                         obs: button1Enabled, 
                         arr: buttonStates 
                       }">button 1</button>
    

    如您所见,这种方法确实为您的视图和视图模型添加了一些额外的代码和复杂性......所以我不确定它是否适合您。不过,它会让您不必实现 MutationObserver,这可能会更加复杂......

    这是一个工作示例:

    ko.bindingHandlers.register = {
      init: function(element, valueAccessor) {
        var settings = valueAccessor();
        
        settings.arr.push(settings.obs);
        
        ko.utils.domNodeDisposal.addDisposeCallback(
          element, 
          function() {
            settings.arr.remove(settings.obs);
          }
        );
      }
    }
    
    var App = function() {
      this.deps = ko.observableArray([]);
      ko.computed(function() {
        // Create dependencies
        this.deps().forEach(function(dep) {
          dep();
        });
    
        renderSub();
      }, this).extend({
        "deferred": true
      });
    
      // Some stuff to mimic an updating vm + ui state
      this.hideAll = ko.observable(false);
      this.count = ko.observable(0);
      this.inc = function() {
        this.count(this.count() + 1);
      }.bind(this);
    
      // Button states
      this.enable1 = ko.pureComputed(function() {
        return this.count() % 2 === 0;
      }, this);
    
      this.enable2 = ko.pureComputed(function() {
        return this.count() % 3 === 0;
      }, this);
    
      this.enable3 = ko.pureComputed(function() {
        return !this.enable1() && !this.enable2();
      }, this);
    }
    
    
    var renderSub = (function() {
      var i = 0;
      return function() {
        // Count renders and show ui is updated
        console.log(
          "Render", 
          ++i,
          Array
            .from(document.querySelector(".tmpl").children)
            .map(c => c.disabled ? 'disabled': 'enabled')
            .join(" ")
        );
      };
    }());
    
    ko.applyBindings(new App());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    <div class="tmpl" data-bind="ifnot: hideAll">
      <button data-bind="enable: enable1, register: { obs: enable1, arr: deps }">button 1</button>
      <button data-bind="enable: enable2, register: { obs: enable2, arr: deps }">button 2</button>
      <button data-bind="enable: enable3, register: { obs: enable3, arr: deps }">button 3</button>
    </div>
    
    <button data-bind="click: inc, text: count() + ' + 1'"></button>
    <button data-bind="click: hideAll.bind(null, !hideAll())">toggle</button>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-24
      • 1970-01-01
      相关资源
      最近更新 更多