我有一个使用Knockout构建的可编辑网格,该网格遵循here讨论的网格。

我希望为hasfocus分配一个可计算的可观察值(与上述链接文章所做的相反,其中hasfocus绑定被分配为与visible相同的可观察对象),但是它不起作用-编辑后的元素未聚焦。

这是我的代码(这是fiddle):



$(document).ready(function() {
  var viewModel = new Grid.ViewModel();
  viewModel.init();
  ko.applyBindings(viewModel);
});

ko.bindingHandlers.clickToEdit = {
   init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
     if (element.className == "editable") {
        var observable = valueAccessor();
        span = document.createElement("span");
        var input = document.createElement("input");

        element.appendChild(span);
        element.appendChild(input);

        ko.applyBindingsToNode(span, {
          text: observable,
          visible: ko.computed(function() { return !viewModel.isEditState(); }),
        });

        ko.applyBindingsToNode(input, {
          value: observable,
          visible: viewModel.isEditState,
          hasfocus: viewModel.shouldFocus, // this seems to not work (but it works if we assign it with isEditState)
        });
      }
    },
}

var Grid = {
  RowViewModel: function() {
     var self = this;
     self.district = ko.observable();
     self.team = ko.observable();
     self.factory = ko.observable();
     self.isEditState = ko.observable(false);
     self.shouldFocus = ko.computed(function() {
    	return self.isEditState();
     });

     self.init = function (data) {
        self.district(data.District);
        self.team(data.Team);
        self.factory(data.Factory);
     }

     self.setEditState = function(){
        self.isEditState(!self.isEditState());
     }
   },

  ViewModel: function() {
     var self = this;
     self.data = ko.observableArray();
     self.init = function() {
         var data = self.getData();
         self.data(data);
     }
     self.getData = function() {
       var data = [];
       var rows = [
         { District: "North", Team: "Jim", Factory: "Mars" },
         { District: "South", Team: "John", Factory: "Pluto" },
       ];
       rows.forEach(function (element, index) {
         var row = new Grid.RowViewModel();
         row.init(element);
         data.push(row);
       });

       return data;
     }
  },
}

td, tr > th {
    text-align: center;
    vertical-align: middle;
}

a {
    text-decoration: none !important;
}

input {
  width: 100%;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>


<div class="grid-container">
  <table class="table table-bordered table-condensed" style="table-layout:fixed">
      <thead>
          <tr class="active">
            <th></th>
            <th>District</th>
            <th>Team</th>
            <th>Factory</th>
            <th>Action</th>
          </tr>
      </thead>
      <tbody data-bind="foreach: data">
          <tr>
            <td>
                <input type="checkbox" />
            </td>
            <td data-bind="text: district, clickToEdit: district"></td>
            <td class="editable" data-bind="clickToEdit: team"></td>
            <td data-bind="text: factory, clickToEdit: factory"></td>
          <td>
            <a href="#" data-bind="visible: !isEditState(), click: setEditState">
              <i class="glyphicon glyphicon-pencil"></i>
            </a>
            <a href="#" data-bind="visible: isEditState(), click: setEditState">
              <i class="glyphicon glyphicon-ok"></i>
            </a>
          </td>
        </tr>
      </tbody>
  </table>
</div>





感兴趣的行是:

self.shouldFocus = ko.computed(function() {
    return self.isEditState();
});


注意:尽管我在这里只使用self.isEditState(),但实际上我会再使用一个或两个表达式,因此需要使用computed

最佳答案

在提出解决方案之前,我想向您介绍问题的minimal, complete, and verifiable example。请注意,查看发生了什么事情容易得多?



const vm = {};

vm.visible = ko.observable(false);
vm.hasFocus = ko.computed(vm.visible);
vm.toggle = () => vm.visible(!vm.visible());

ko.applyBindings(vm);

<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<input data-bind="visible: visible, hasFocus: hasFocus">
<button data-bind="click: toggle">toggle</button>





通过这种重构,我发现:问题可能是同时处理visiblehasFocus更新,而没有给DOM足够的时间来处理这两个问题吗?毕竟,浏览器无法将焦点添加到隐藏的元素上。

让我们尝试使hasFocus计算为deferred。延迟的计算在事件循环的末尾运行,就像setImmediate(cb)setTimeout(cb, 0)这样。



const vm = {};

vm.visible = ko.observable(false);
vm.hasFocus = ko.computed(vm.visible)
  .extend({ deferred: true });
vm.toggle = () => vm.visible(!vm.visible());

ko.applyBindings(vm);

<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<input data-bind="visible: visible, hasFocus: hasFocus">
<button data-bind="click: toggle">toggle</button>





成功!现在,敲除会更新输入的可见性,给DOM时间更新,然后才添加焦点。

09-25 15:18