Search code examples
knockout.jsknockout-mapping-plugin

How to add parent function to KnockOut double-nested mapping?


I'm using KnockOut mapping fromJS to create my View Model from a JSON object like the following:

{
    "cats": [{
        "name": "fluffy",
        "color": "brown",
        "kittens": [{
            "name": "spot",
            "color": "brown"
        }, {
            "name": "rascal",
            "color": "grey"
        }, {
            "name": "trouble",
            "color": "white"
        }]
    }, {
        "name": "kitty",
        "color": "red",
        "kittens": [{
            "name": "lady",
            "color": "red"
        }, {
            "name": "skat",
            "color": "striped"
        }]
    }]
}

html:

<div data-bind="foreach:cats">
    <span data-bind="text:name"></span>
    <table>
        <tr data-bind="foreach: kittens">
             <td data-bind="text:name"></td>
             <td data-bind="text:color"></td>
             <td><a data-bind="click: $parent:showParentColor" href="#">Parent Color</a></td>
        </tr>
    </table>
</div>

Javascript:

var KittenModel = function (data) {
    ko.mapping.fromJS(data, {}, this);

    // ... various computed values added to this
}

var mapping = {
    'kittens': {
        create: function(options) {
            return new KittenModel(options.data);
        }
    },
    'otherItem': {
        create: function(options) {
             return ('otherStuff');
        }
     }
}

var data = { ... }; // the JSON above

var CatsViewModel = ko.mapping.fromJS(data, mapping);

Question:

Where and how do I put the showParentColor() function so that the the data-bind works in the kitten table? For example:

function showParentColor(cat) {
    alert(cat.color);
}

Thanks!


Solution

  • You may use one of the following based on your view model hierarchy :

    • $root : This points to the main view model object in the root context.The top most parent context.
    • $parents array : This is an array which contains all your view models.

      • $parents[0] : The parent view model context.(also it’s the same as $parent)

      • $parents[1]: The second parent view model context.(grand parent)

      • $parents[2]: The third parent view model context . (great-grand parent)

      • And so on....


    Update : If you want to add a function in CatsViewModel level, you simply add your function to created model.

    Example :https://jsfiddle.net/kyr6w2x3/87/

    JS:

     CatsViewModel.showParentColor = function(item){
          console.log(item.name());
          console.log(item.color());
        }
    

    View:

    <a data-bind="click: $parents[1].showParentColor">
    

    Below is the hierarchy of your model

    - CatsViewModel 
        - cats : observableArray
            - name : observable
            - color : observable
            - kittens : observableArray
                   - name : observable
                   - color : observable
        - showParentColor : function
    


    Alternative : You can do the job yourself and create your models.Would be easier for you to modify and maintain based on what you want.You can also add click function inside any models you want.

    Example :http://jsfiddle.net/kyr6w2x3/91/

    HTML :

    <div data-bind="foreach:cats">
        <span data-bind="text:name"></span>
        <table>
        <tbody data-bind="foreach: kittens">
           <tr>
                 <td data-bind="text:name"></td>
                 <td data-bind="text:color"></td>
                 <td><a data-bind="click: $parent.showParentColor" href="#">Parent Color</a></td>
            </tr>
        </tbody>
    
        </table>
    </div>
    

    JS:

     var data = {
        "cats": [{
            "name": "fluffy",
            "color": "brown",
            "kittens": [{
                "name": "spot",
                "color": "brown"
            }, {
                "name": "rascal",
                "color": "grey"
            }, {
                "name": "trouble",
                "color": "white"
            }]
        }, {
            "name": "kitty",
            "color": "red",
            "kittens": [{
                "name": "lady",
                "color": "red"
            }, {
                "name": "skat",
                "color": "striped"
            }]
        }]
    }
    
    var CatsViewModel = function (data){ 
      var self = this;
      self.cats = ko.observableArray($.map(data.cats, function (item) {
            return new CatItemViewModel(item);
        }));
    }
    var CatItemViewModel = function (data){
      var self = this;
      self.name = ko.observable(data.name);
      self.color = ko.observable(data.color);
      self.kittens = ko.observableArray($.map(data.kittens, function (item)     {
       var newData = Object.assign({}, item, { parent: self.name()});
            return new KittenModel(newData);
       }));
       self.showParentColor = function (item){
          console.log("Parent Name: " , self.name());
          console.log("Name: " , item.name());
          console.log("Color: " , item.color());
       }
    }
    var KittenModel = function (data) {
      var self = this;
      self.name = ko.observable(data.name);
      self.color = ko.observable(data.color);
      self.parent = ko.observable(data.parent);
    }
    var vm = new CatsViewModel(data);
    ko.applyBindings(vm);