0

I'm getting this kind of data from the server

{
    FirmName: "Firm Name",
    Address: "123 Address",
    AltNames: [
        "First Alt Name",
      "Second Alt Name"
    ]
  }

I'm using knockout mappings plugin to map the data to a viewModel with ko.mapping.fromJS(data, {}, this);

However, anytime I change any string array element, my changes are not propagated to the viewModel. What do I do to fix the problem?

My js:

function firmModel() {
    //this is what I get from the server
  var data = {
    FirmName: "Firm Name",
    Address: "123 Address",
    AltNames: [
        "First Alt Name",
      "Second Alt Name"
    ]
  };
  
  ko.mapping.fromJS(data, {}, this);
  
}

var em = new firmModel();
ko.applyBindings(firmModel);

My html:

<div>
  <label>Company</label>
  <input type="text" data-bind="value: FirmName">
</div>
<div>
  <label>Address</label>
  <input type="text" data-bind="value: Address">
</div>
<br>
<h3>
Alt Names
</h3>
<ul data-bind="foreach: AltNames">
  <li>
    <input type="text" data-bind="value: $data">
  </li>
</ul>

<br/><br/>
<h2>
Result of the changes where I see whether viewModel picks up my changes above
</h2> 
<span>Company: </span><span data-bind="text: FirmName"></span><br/>
<span>Address: </span><span data-bind="text: Address"></span><br/>
<br/>
Alt Names: <br/>
<ul data-bind="foreach: AltNames">
  <li data-bind="text: $data"></li>
</ul>

Fiddle http://jsfiddle.net/n87esdLv/2/

0

1 Answer 1

1

In the foreach you can't use $data:

<input type="text" data-bind="value: $data">

as you can read in documentation:

$rawData

This is the raw view model value in the current context. Usually this will be the same as $data, but if the view model provided to Knockout is wrapped in an observable, $data will be the unwrapped view model, and $rawData will be the observable itself.

Then, you need to use $rawData instead, because the content of the AltNames array must be observables, not strings.

The strings in the AltNames must be observables. You need to customize the creation of the AltNames array with custom object construction using “create”:

var mapping = {
        'AltNames': {
                create: function(options) {
                        return ko.observable(options.data);
                }
        }
}

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

This is a runnable example:

function firmModel() {
    //this is what I get from the server
  var data = {
    FirmName: "Firm Name",
    Address: "123 Address",
    AltNames: [
        "First Alt Name",
      "Second Alt Name"
    ]
  };
  
  //ko.mapping.fromJS(data, {}, this);
    var mapping = {
            'AltNames': {
                    create: function(options) {
                            return ko.observable(options.data);
                    }
            }
    }

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

var em = new firmModel();
ko.applyBindings(firmModel);
h2{
  font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<div>
  <label>Company</label>
  <input type="text" data-bind="value: FirmName">
</div>
<div>
  <label>Address</label>
  <input type="text" data-bind="value: Address">
</div>
<br>
<h3>
Alt Names
</h3>
<ul data-bind="foreach: AltNames">
  <li>
    <input type="text" data-bind="value: $rawData">
  </li>
</ul>

<br/><br/>
<h2>
Result of the changes
</h2> 
<span>Company: </span><span data-bind="text: FirmName"></span><br/>
<span>Address: </span><span data-bind="text: Address"></span><br/>
<br/>
Alt Names: <br/>
<ul data-bind="foreach: AltNames">
  <li data-bind="text: $data"></li>
</ul>

Sign up to request clarification or add additional context in comments.

4 Comments

Jose, thank you so much! I've got a follow up question. What if AltNames is not a root element. For example, ` {FirmNames: {MainName: "Bla", AltNames: ["One", "Two]}} ? I've tried using "FirmNames.AltNames": { create: function(options) { return ko.observable(options.data); } } ` but it didn't work
Can you change this array of strings to an array of objects: {FirmNames: {MainName: "Bla", AltNames: [{name:"One"},{name: "Two}]}} Then, the plugin will do the job without the need of the mapping. I add name: in AltNames.
In this fiddle jsfiddle.net/b2L3nmc5/3 I use the same mapping and I think it works.
Thank you, Jose! You're right. I needed to use $rawData instead of $data in ko loop. Thanks again!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.