简体   繁体   中英

Bindings doesn't work on nested template loaded by JSON in KnockoutJS

Yesterday I make this question: How can I refresh or load JSON to my viewModel on Knockout JS with complex models

Everything works OK with the fixes but when I try to use a complex json to load in the viewModel some of the buttons (specifically on Groups) doesn't work.

To resume the problem. I have a json with the previous serialized data. I use that json to fill the viewModel, this works, load correctly the data but the problem are in the "group" template, because the data is loaded but the buttons doesn't work, the only button which is working is the "remove group". (Please refer to the image)

Any idea to fix this? Thanks.

Jsfiddle example with the problem http://jsfiddle.net/y98dvy56/26/

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

Here is the 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);

Your issue is caused by the fact that the mapping plugin makes your data observable, but doesn't augment your data with the functions in your model such as the add, remove, etc... functions. If you do a console log for the json data when it's inserted into the view model you will notice that the data is observable but the functions are missing. You need to provide a mapping to customize your Group, Condition, etc.. constructors. Because the children array in your case is of mixed types (condition or group) Here is a custom mapping to take care of that:

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

Then you simply need to provide this mapping in your initial mapping

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

Then inside the constructor of the Group object:

 function Group(data){

   var self = this;

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

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

You also need to update the Condition constructor to accept the data provided by the mapping, but since conditions don't have children you do not need to provide the childrenMapping here:

 function Condition(data){

     var self = this;

     self.templateName = 'condition-template';

     ...

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

I've the mapping at the end of both function so that the mapped values override you initial value.

The updated jsfiddle here:

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

This answer is related: knockout recursive mapping issue

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM