Search code examples
knockout.jsknockout-mapping-plugin

Add and remove object from array when the observable created from knockout.mapping


The following is a very simplified version of my code..

My html code:

<div class="container body-content" data-bind="foreach:base">
    <div class="col-md-12" data-bind="with:s">
        <div class="col-md-4"><pre data-bind="text:sp"></pre></div>
        <div class="col-md-7">
            <div class="row" data-bind="foreach: spd">
                <input type="text" class="form-control" data-bind="value:value, valueUpdate: 'input'" />
                <button data-bind="click: $parent.addval">add</button><button data-bind="click: $parent.removeval">rem</button>
            </div>
        </div>
    </div>
</div>

My Javascript code:

<script src="knockout-3.2.0.js"></script>
            <script src="knockout.mapping.js"></script>
            <script>
                var data = [{
                    "s": {
                        "sp": "abc",
                        "spd": [
                          {
                              "value": ""
                          }
                        ]
                    },
                    "type": "xyz",
                 }];
                var AppScope = function () {

                    function BaseViewModel() {
                        var self = this;
                        self.base = ko.observableArray();
                        self.base(ko.mapping.fromJS(data)());
                    }
                    ko.applyBindings(new BaseViewModel());
                }();
            </script>

The 'data' array comes from server and has a much complex data structure, so $root can not be used. The style used here may be the answer but not able to figure it out yet..

The code works except for the buttons. I would like to understand how to add {value:""} object to the array under 'spd' and remove the same on press of addval and remval functions.

All help is sincerely appreciated Thanks


Solution

  • This is kind of a weird setup, but I'm just going with what you provided. The spd initially has one member with an empty value. The input box allows you to change the value. The add button duplicates the value into a new entry. The remove button removes the current entry.

    The trickiest bit is figuring out the structure from self.base down to the spd observableArray. Update: I've modified the click binding handlers to take the s context (which is $parent) so they know which s's spd they're working with.

    I changed the context from $parent to $root because it didn't make sense to me to create addval and removeval functions at the s level.

    var data = [{
      "s": {
        "sp": "abc",
        "spd": [{
          "value": ""
        }]
      },
      "type": "xyz",
    }];
    var AppScope = function() {
    
      function BaseViewModel() {
        var self = this;
        self.base = ko.observableArray();
        self.base(ko.mapping.fromJS(data)());
        self.addval = function(sData, data) {
          sData.spd.push({value: ko.observable(data.value())});
        };
        self.removeval = function(sData, data) {
          sData.spd.remove(data);
        }
    
      }
      ko.applyBindings(new BaseViewModel());
    }();
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
    <div class="container body-content" data-bind="foreach:base">
      <div class="col-md-12" data-bind="with:s">
        <div class="col-md-4"><pre data-bind="text:sp"></pre>
        </div>
        <div class="col-md-7">
          <div class="row" data-bind="foreach: spd">
            <input type="text" class="form-control" data-bind="value:value, valueUpdate: 'input'" />
            <button data-bind="click: (data) => $root.addval($parent, data)">add</button>
            <button data-bind="click: (data) => $root.removeval($parent, data)">rem</button>
          </div>
        </div>
      </div>
    </div>

    If you wanted to do the same sort of thing using unobtrusive event handling (and you have jQuery), the relevant code might look like this:

    function BaseViewModel() {
        var self = this;
        self.base = ko.observableArray();
        self.base(ko.mapping.fromJS(data)());
      }
      ko.applyBindings(new BaseViewModel());
    
      $('body').on('click', '.add-btn', function() {
        const context = ko.contextFor(this);
        const data = ko.dataFor(this);
        const s = context.$parent;
    
        s.spd.push({
          value: ko.observable(data.value())
        });
      });
    
      $('body').on('click', '.remove-btn', function() {
        const context = ko.contextFor(this);
        const data = ko.dataFor(this);
        const s = context.$parent;
    
        s.spd.remove(data);
      });