绑定不适用于KnockoutJS中由JSON加载的嵌套模板

时间:2015-06-12 22:43:31

标签: javascript json mvvm knockout.js

昨天我提出这个问题: How can I refresh or load JSON to my viewModel on Knockout JS with complex models

一切正常,但是当我尝试使用复杂的json加载到viewModel时,某些按钮(特别是在Groups上)不起作用。

恢复问题。我有一个json与以前的序列化数据。我用json填充viewModel,这个工作,正确加载数据但问题出在“组”模板中,因为数据已加载但按钮不起作用,唯一有效的按钮是“删除”组”。 (请参阅图片)

有什么想法解决这个问题吗?感谢。

有问题的Jsfiddle示例 http://jsfiddle.net/y98dvy56/26/

Check this picture. The red circles indicates the buttons with problems. The green circles indicates the buttons without problems.

这是body html

  <div class="container">
    <h1>Knockout.js Query Builder</h1>
    <div class="alert alert-info">
      <strong>Example Output</strong><br/>

    </div>
    <div data-bind="with: group">
      <div data-bind="template: templateName"></div>
    </div>
    <input type="submit" value="Save" data-bind="click: Save"/>
  </div>

  <!-- HTML Template For Conditions -->
  <script id="condition-template" type="text/html">
    <div class="condition">
      <select data-bind="options: fields, value: selectedField"></select>
      <select data-bind="options: comparisons, value: selectedComparison"></select>
      <input type="text" data-bind="value: value"></input>
      <button class="btn btn-danger btn-xs" data-bind="click: $parent.removeChild"><span class="glyphicon glyphicon-minus-sign"></span></button>
    </div>
  </script>

  <!-- HTML Template For Groups -->
  <script id="group-template" type="text/html">
    <div class="alert alert-warning alert-group">
      <select data-bind="options: logicalOperators, value: selectedLogicalOperator"></select>
      <button class="btn btn-xs btn-success" data-bind="click: addCondition"><span class="glyphicon glyphicon-plus-sign"></span> Add Condition</button>
      <button class="btn btn-xs btn-success" data-bind="click: .addGroup"><span class="glyphicon glyphicon-plus-sign"></span> Add Group</button>
      <button class="btn btn-xs btn-danger" data-bind="click: $parent.removeChild"><span class="glyphicon glyphicon-minus-sign"></span> Remove Group</button>
      <div class="group-conditions">
        <div data-bind="foreach: children">
          <div data-bind="template: templateName"></div>
        </div>
      </div>
    </div>
  </script>

  <!-- js -->
  <script src="js/vendor/knockout-2.2.1.js"></script>
  <script src="js/vendor/knockout-mapping.js"></script>
  <script src="js/condition.js"></script>
  <script src="js/group.js"></script>
  <script src="js/viewModel.js"></script>
  <script>
  window.addEventListener('load', function(){
  var json = 
{"group":{"templateName":"group-template","children":[{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"group-template","children":[{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"},{"templateName":"condition-template","fields":["Points","Goals","Assists","Shots","Shot%","PPG","SHG","Penalty Mins"],"selectedField":"Points","comparisons":["=","<>","<","<=",">",">="],"selectedComparison":"=","value":0,"text":"Points = 0"}],"logicalOperators":["AND","OR"],"selectedLogicalOperator":"AND","text":"(Points = 0 AND Points = 0 AND Points = 0)"}],"logicalOperators":["AND","OR"],"selectedLogicalOperator":"AND","text":"(Points = 0 AND Points = 0 AND Points = 0 AND (Points = 0 AND Points = 0 AND Points = 0))"},"text":"(Points = 0 AND Points = 0 AND Points = 0 AND (Points = 0 AND Points = 0 AND Points = 0))"};

    var vm = new QueryBuilder.ViewModel();
    ko.mapping.fromJS(json.group, {}, vm.group);
    ko.applyBindings(vm);   

  }, true);
  </script>

Condition.js:

window.QueryBuilder = (function(exports, ko){

  function Condition(){
    var self = this;

    self.templateName = 'condition-template';

    self.fields = ko.observableArray(['Points', 'Goals', 'Assists', 'Shots', 'Shot%', 'PPG', 'SHG', 'Penalty Mins']);
    self.selectedField = ko.observable('Points');

    self.comparisons = ko.observableArray(['=', '<>', '<', '<=', '>', '>=']);

    self.selectedComparison = ko.observable('=');

    self.value = ko.observable(0);
  }

  exports.Condition = Condition;
  return exports;

})(window.QueryBuilder || {}, window.ko);

Group.js

window.QueryBuilder = (function(exports, ko){

  var Condition = exports.Condition;

  function Group(){
    var self = this;

    self.templateName = 'group-template';
    self.children = ko.observableArray();
    self.logicalOperators = ko.observableArray(['AND', 'OR']);
    self.selectedLogicalOperator = ko.observable('AND');

    // give the group a single default condition
    self.children.push(new Condition());

    self.addCondition = function(){
        self.children.push(new Condition());
    };

    self.addGroup = function(){
        self.children.push(new Group());
    };

    self.removeChild = function(child){
        self.children.remove(child);
    };
  }

  exports.Group = Group;
  return exports;

})(window.QueryBuilder || {}, window.ko);

ViewModel.js

window.QueryBuilder = (function(exports, ko){

  var Group = exports.Group;

  function ViewModel() {
    var self = this;
    self.group = ko.observable(new Group());

    self.load = function (data) {
        ko.mapping.fromJS(data, self);
    }   

    self.Save = function () {
        console.log(ko.toJSON(self));
    }   
  }

  exports.ViewModel = ViewModel;
  return exports;

})(window.QueryBuilder || {}, window.ko);

1 个答案:

答案 0 :(得分:2)

您的问题是由于映射插件使您的数据可观察,但不会使用模型中的函数(如add,remove等函数)扩充数据。如果在json数据插入视图模型时执行控制台日志,您会注意到数据是可观察的,但缺少函数。您需要提供映射来自定义您的组,条件等构造函数。因为你的案例中的children数组是混合类型(条件或组)这是一个自定义映射来处理:

var childrenMapping = {
    'children': {
        create: function(options) {
            var data = options.data;
            console.log(data);
            var object;
            switch(data.templateName) {
                case 'condition-template':
                    object = new QueryBuilder.Condition(data);
                    break;
                case 'group-template':
                    object = new QueryBuilder.Group(data);
                    break;
            }
            return object;
        }
    }
};      

然后,您只需在初始映射中提供此映射

ko.mapping.fromJS(json.group, childrenMapping, vm.group);

然后在Group对象的构造函数中:

 function Group(data){

   var self = this;

   self.templateName = 'group-template';
   ...

   ko.mapping.fromJS(data, childrenMapping, this);    
}

您还需要更新Condition构造函数以接受映射提供的数据,但由于条件没有子级,因此您不需要在此处提供childrenMapping:

 function Condition(data){

     var self = this;

     self.templateName = 'condition-template';

     ...

     ko.mapping.fromJS(data, {}, this);    
 }

我在两个函数的末尾都有映射,以便映射的值覆盖你的初始值。

这里更新的jsfiddle:

http://jsfiddle.net/omerio/y98dvy56/32/

这个答案是相关的: knockout recursive mapping issue