0
votes

I have a knockout view model which consists of an observableArray of objects.

I am usig this json data to populate the array

This JSON structure is a simplified version of my real structure. In that, I want to give several attributes to one item, for example to control whether a form input is enabled or not: {"name":"George", "input_disabled":"true", "input_maxlength":"50} etc

function listItem(id, name, address) {
  var self = this;
  self.id = ko.observable(id);
  self.name = ko.observable(name);
  self.address = ko.observable(address);
}
function vModel(data) {
	var self = this;
	self.listArray = ko.observableArray();
  $.each(data, function(key, val) {
  	self.listArray.push(new listItem(val.id, val.name, val.address));
  });
	return {
		listArray : self.listArray
	}
}

var jsondata = [
{"id":"1"}, {"name":"George"}, {"age": "35"}, {"occupation": "Architect"}, {"address":"NY"},
{"id":"2"}, {"name":"Jerry"}, {"age": "35"}, {"occupation": "Comedian"}, {"address":"Brooklyn"},
{"id":"3"}, {"name":"Elaine"}, {"age": "35"}, {"occupation": "Publisher"}, {"address":"Manhattan"}
];

ko.applyBindings(new vModel(jsondata));
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table class="table table-striped">
  <tbody data-bind="foreach: listArray()">
    <tr>
      <td>ID</td>
      <td data-bind="text: id"></td>
    </tr>
    <tr>
      <td>Name</td>
      <td data-bind="text: name"></td>
    </tr>
    <tr>
      <td>Address</td>
      <td data-bind="text: address"></td>      
    </tr>
  </tbody>
</table>

When I populate the array with the included JSON, the foreach loop prints out too many rows. As per the knockout documentation, the foreach loop duplicates the html elements for each item in the array.

But the question is, is there any way possible for me to get this JSON working with knockout? Or do I have to change the JSON similar to: var jsondata = [[{"id":"1"}, {"name":"George"}]] etc

2
you need an array of single object for each row like var jsondata = [{ "id": "1", "name": "George", "age": "35", "occupation": "Architect", "address": "NY" }, { "id": "2", "name": "Jerry", "age": "35", "occupation": "Comedian", "address": "Brooklyn" } ]Matin Kajabadi
Thank you for this, I will change the incoming JSON.Jaakko Melolinna
If you can, you should definitely change the data format server side. If you can't, you can cut up the array in sections of 5 key value pairs and merge those using Object.assign. For example, in an ugly one-liner: jsondata.reduce((res, kvp, i) => (i % 5 ? Object.assign(res[res.length - 1], kvp) : res.push(kvp), res), [])user3297291

2 Answers

0
votes

Knockout foreach binds to each element in the array. By defining the json structure like you did, you would have 15 rows in the table. You also have no way of knowing what id corresponds to what name.

I've changed your JSON structure as follows:

[{ "id": "1", "name": "George", "age": "35", "occupation": "Architect", 
"address": "NY" },
{ "id": "2", "name": "Jerry", "age": "35", "occupation": 
"Comedian", "address": "Brooklyn" },
{"id":"3", "name":"Elaine", "age": "35", "occupation": "Publisher", 
"address":"Manhattan"}] 

function listItem(id, name, address) {
  var self = this;
  self.id = ko.observable(id);
  self.name = ko.observable(name);
  self.address = ko.observable(address);
}
function vModel(data) {
	var self = this;
	self.listArray = ko.observableArray();
  $.each(data, function(key, val) {
  	self.listArray.push(new listItem(val.id, val.name, val.address));
  });
	return {
		listArray : self.listArray
	}
}

var jsondata = 
[{ "id": "1", "name": "George", "age": "35", "occupation": "Architect", "address": "NY" }, { "id": "2", "name": "Jerry", "age": "35", "occupation": "Comedian", "address": "Brooklyn" },
{"id":"3", "name":"Elaine", "age": "35", "occupation": "Publisher", "address":"Manhattan"}
];

ko.applyBindings(new vModel(jsondata));
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table class="table table-striped">
  <tbody data-bind="foreach: listArray()">
    <tr>
      <td>ID</td>
      <td data-bind="text: id"></td>
    </tr>
    <tr>
      <td>Name</td>
      <td data-bind="text: name"></td>
    </tr>
    <tr>
      <td>Address</td>
      <td data-bind="text: address"></td>      
    </tr>
  </tbody>
</table>
0
votes

Going a bit deeper: if I wanted to build a form based on that data, how would I go about it?

Let's say I wanted to show each row as part of the form. Then the JSON data would also be used to control the input element. For example: I want to define in my JSON that "id" is a hidden field and "name" is read only. This would be applied differently to each set of data, based on the JSON.

<table class="table">
<tr><td><input type="hidden" name="id" value="1" /></td></tr>
<tr><td><input type="text" name="name" readonly="readonly" value="George" /></td></tr>
</table>
<table class="table">
<tr><td><input type="hidden" name="id" value="2" /></td></tr>
<tr><td><input type="text" name="name" disabled="disabled" value="Jerry" /></td></tr>
</table>
<table class="table">
<tr><td><input type="hidden" name="id" value="3" /></td></tr>
<tr><td><input type="text" name="name" size="75" value="Elaine" /></td></tr>
</table>