Displaying nested JSON data using Knockout Mapping - json

I'm trying to use Knockout's mapping plugin on a nested JSON object with variable data inside. However, I'm not sure how to get it to display in my HTML. How do I correctly map all the nested JSON objects and display it as, say, a simple string? Here is my code:
JS
var ListModel = function(jsonData) {
var self = this;
self.master = ko.mapping.fromJS(jsonData);
}
var listModel = new ListModel(jsonData);
ko.applyBindings(listModel);
HTML
<!-- ko foreach: master -->
<div data-bind="text: $data"></div>
<!-- /ko -->
Sample JSON
{"Level 1a":"Hi","Level 1b":{
"Level 2a":"Hello","Level 2b":{
"Level 3":"Bye"}
}
}
Sample Output
Hi
Hello
Bye
The main thing I'm trying to do here is to print out the values from all nested levels. The key values and number of nested levels are entirely variable (most of the nested JSON examples I found on SO and online were for fixed keys). Is this possible?
Update: I found the jQuery equivalent, but I still need the Knockout implementation for observables.

Since your JSON object has variable keys, you must transform it into a fixed, predictable structure first or nested template mapping will not work (knockout is declarative, so you need to know key names beforehand).
Consider the following custom mapping code (no knockout mapping plugin needed):
var ListModel = function(jsonData) {
var self = this;
self.master = ko.observableArray([]);
function nestedMapping(data, level) {
var key, value, type;
for (key in data) {
if (data.hasOwnProperty(key)) {
if (data[key] instanceof Object) {
type = "array";
value = ko.observableArray([]);
nestedMapping(data[key], value());
} else {
type = "simple";
value = ko.observable(data[key]);
}
level.push({key: key, type: type, value: value});
}
}
}
nestedMapping(jsonData, self.master());
}
the function nestedMapping() turns your data structure:
{
"Level 1a": "Hi",
"Level 1b": {
"Level 2a": "Hello",
"Level 2b": {
"Level 3": "Bye"
}
}
}
into:
[
{
"key": "Level 1a",
"type": "simple",
"value": "Hi"
},
{
"key": "Level 1b",
"type": "array",
"value": [
{
"key": "Level 2a",
"type": "simple",
"value": "Hello"
},
{
"key": "Level 2b",
"type": "array",
"value": [
{
"key": "Level 3",
"type": "simple",
"value": "Bye"
}
]
}
]
}
]
Now you can create a template like this one:
<script type="text/html" id="nestedTemplate">
<!-- ko if: type == 'simple' -->
<div class="name" data-bind="text: value, attr: {title: key}"></div>
<!-- /ko -->
<!-- ko if: type == 'array' -->
<div class="container" data-bind="
template: {
name: 'nestedTemplate',
foreach: value
}
"></div>
<!-- /ko -->
</script>
See it working: http://jsfiddle.net/nwdhJ/2/
Note a subtle but important point about nestedMapping(). It creates nested observables/observableArrays. But it works with the native array instances (by passing self.master() and value() into the recursion).
This way you avoid needless delay during object construction. Every time you push values to an observableArray it triggers knockout change tracking, but we don't need that. Working with the native array will be considerably faster.

Change your JSON data to this (note that the arrays!):
[
{
"Text": "Hi",
"Children": [
{
"Text": "Hello",
"Children": [
{
"Text": "Bye"
}
]
}
]
}
]
and use a self-referential template:
<script type="text/html" id="nestedTemplate">
<div class="name" data-bind="text: Text"></div>
<div class="container" data-bind="
template: {
name: 'nestedTemplate',
foreach: Children
}
"></div>
</script>
that you call like this:
<div class="container" data-bind="
template: {
name: 'nestedTemplate',
foreach: master
}
"></div>
You can then use CSS to manage indent:
/* indent from second level only */
div.container div.container {
margin-left: 10px;
}
See it on jsFiddle: http://jsfiddle.net/nwdhJ/1/

Related

How to output response of type atom/xml feed into Jquery DataTable?

How to output response of type atom/xml feed (from arxiv call) into Jquery DataTable?
I have the datatable working for a simple json from Ajax call to flask server example.
When i try to do it with the xml from an arxiv api response, i cant seem to get it to display in the datatable (though i can just print the raw xml using <pre lang="xml" > or json).
I also tried to convert to json first via python dictionary, but still couldnt get it formatted into datatable as im unsure how to access the properties properly in the Ajax call when theyre deeper than the first level as in the basic example linked.
The HTML in template:
<table id="arxivtable" class="display" style="width:100%">
<thead>
<tr>
<th>title</th>
<th>id</th>
<th>link</th>
<th>author</th>
<th>published</th>
</tr>
</thead>
</table>
I tried via xml :
$('#arxivtable').DataTable({
"ajax": {
// "url": "static/objects2.txt", // This works for the static file
"url": "/get_arxivpapers", // This now works too thanks to #kthorngren
"dataType": "xml",
"type":"GET",
"dataSrc": "{{name}}",
"contentType":"application/atom+xml"
},
"columns": [
{"data": "title"},
{
"data": "link",
"render": function(data, type, row, meta){
if(type === 'display'){
data = '' + data + '';
}
return data;
}
},
{ "data": "id" },
{ "data": "link" },
{ "data": "author" },
{ "data": "journal" },
{ "data": "published" },
{ "data": "summary" }
]
});
JSON from AJAX call:
{
"feed": {
"#xmlns": "http://www.w3.org/2005/Atom",
"link": {
"#href": "http://arxiv.org/api/query?search_query%3Dall%3Aeinstein%26id_list%3D%26start%3D0%26max_results%3D2",
"#rel": "self",
"#type": "application/atom+xml"
},
"title": {
"#type": "html",
"#text": "ArXiv Query: search_query=all:einstein&id_list=&start=0&max_results=2"
},
"id": "http://arxiv.org/api/vehKAQR+bheXtHwJw3qx/OG/XXw",
"updated": "2022-06-14T00:00:00-04:00",
"opensearch:totalResults": {
"#xmlns:opensearch": "http://a9.com/-/spec/opensearch/1.1/",
"#text": "36970"
},
"opensearch:startIndex": {
"#xmlns:opensearch": "http://a9.com/-/spec/opensearch/1.1/",
"#text": "0"
},
"opensearch:itemsPerPage": {
"#xmlns:opensearch": "http://a9.com/-/spec/opensearch/1.1/",
"#text": "2"
},
"entry": [
{
"id": "http://arxiv.org/abs/1801.05533v2",
"updated": "2018-11-22T14:04:43Z",
"published": "2018-01-17T03:05:51Z",
"title": "Einstein-Weyl structures on almost cosymplectic manifolds",
"summary": "",
"author": {
"name": "Xiaomin Chen"
},
"arxiv:comment": {
"#xmlns:arxiv": "http://arxiv.org/schemas/atom",
"#text": "accepted by Periodica Mathematica Hungarica, 14 pages, no figures"
},
"link": [
{
"#href": "http://arxiv.org/abs/1801.05533v2",
"#rel": "alternate",
"#type": "text/html"
},
{
"#title": "pdf",
"#href": "http://arxiv.org/pdf/1801.05533v2",
"#rel": "related",
"#type": "application/pdf"
}
],
"arxiv:primary_category": {
"#xmlns:arxiv": "http://arxiv.org/schemas/atom",
"#term": "math.DG",
"#scheme": "http://arxiv.org/schemas/atom"
},
"category": [
{
"#term": "math.DG",
"#scheme": "http://arxiv.org/schemas/atom"
},
{
"#term": "53D10, 53D15",
"#scheme": "http://arxiv.org/schemas/atom"
}
]
},
{
"id": "http://arxiv.org/abs/0802.2137v3",
"updated": "2008-04-01T04:36:21Z",
"published": "2008-02-15T04:40:56Z",
"title": "",
"summary": ".",
"author": {
"name": ""
},
"arxiv:comment": {
"#xmlns:arxiv": "http://arxiv.org/schemas/atom",
"#text": "18 pages, added Theorem 5"
},
"link": [
{
"#href": "http://arxiv.org/abs/0802.2137v3",
"#rel": "alternate",
"#type": "text/html"
},
{
"#title": "pdf",
"#href": "http://arxiv.org/pdf/0802.2137v3",
"#rel": "related",
"#type": "application/pdf"
}
],
"arxiv:primary_category": {
"#xmlns:arxiv": "http://arxiv.org/schemas/atom",
"#term": "math.DG",
"#scheme": "http://arxiv.org/schemas/atom"
},
"category": [
{
"#term": "math.DG",
"#scheme": "http://arxiv.org/schemas/atom"
},
{
"#term": "53C30; 53C25",
"#scheme": "http://arxiv.org/schemas/atom"
}
]
}
]
}
}
Or the original atom/xml:
<feed xmlns="http://www.w3.org/2005/Atom">
<link href="http://arxiv.org/api/query?search_query%3Dall%3Aeinstein%26id_list%3D%26start%3D0%26max_results%3D2" rel="self" type="application/atom+xml">
<title type="html">ArXiv Query: search_query=all:einstein&id_list=&start=0&max_results=2</title>
<id>http://arxiv.org/api/vehKAQR+bheXtHwJw3qx/OG/XXw</id>
<updated>2022-06-14T00:00:00-04:00</updated>
<opensearch:totalresults xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">36970</opensearch:totalresults>
<opensearch:startindex xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">0</opensearch:startindex>
<opensearch:itemsperpage xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">2</opensearch:itemsperpage>
<entry>
<id>http://arxiv.org/abs/1801.05533v2</id>
<updated>2018-11-22T14:04:43Z</updated>
<published>2018-01-17T03:05:51Z</published>
<title></title>
<summary>
</summary>
<author>
<name></name>
</author>
<arxiv:comment xmlns:arxiv="http://arxiv.org/schemas/atom">accepted by Periodica Mathematica Hungarica, 14 pages, no figures</arxiv:comment>
<link href="http://arxiv.org/abs/1801.05533v2" rel="alternate" type="text/html">
<link title="pdf" href="http://arxiv.org/pdf/1801.05533v2" rel="related" type="application/pdf">
<arxiv:primary_category xmlns:arxiv="http://arxiv.org/schemas/atom" term="math.DG" scheme="http://arxiv.org/schemas/atom">
<category term="math.DG" scheme="http://arxiv.org/schemas/atom">
<category term="53D10, 53D15" scheme="http://arxiv.org/schemas/atom">
</category></category></arxiv:primary_category></entry>
<entry>
<id>http://arxiv.org/abs/0802.2137v3</id>
<updated>2008-04-01T04:36:21Z</updated>
<published>2008-02-15T04:40:56Z</published>
<title></title>
<summary>
</summary>
<author>
<name></name>
</author>
<arxiv:comment xmlns:arxiv="http://arxiv.org/schemas/atom"></arxiv:comment>
<link href="http://arxiv.org/abs/0802.2137v3" rel="alternate" type="text/html">
<link title="pdf" href="http://arxiv.org/pdf/0802.2137v3" rel="related" type="application/pdf">
<arxiv:primary_category xmlns:arxiv="http://arxiv.org/schemas/atom" term="math.DG" scheme="http://arxiv.org/schemas/atom">
<category term="math.DG" scheme="http://arxiv.org/schemas/atom">
<category term="53C30; 53C25" scheme="http://arxiv.org/schemas/atom">
</category></category></arxiv:primary_category></entry>
</feed>
The End Point:
#app.route('/get_arxivpapers')
def getArxivPapers(name="einstein"):
max_results = 2
searchterm = name.replace("_", "&#32")
url = 'http://export.arxiv.org/api/query?search_query=all:' + searchterm + '&start=0&' + 'max_results='+ str(max_results)
data = urllib.request.urlopen(url)
# data_dict = xmltodict.parse(data)
# json_data = json.dumps(data_dict)
# print(json_data)
# return jsonify(json_data)
return data.read().decode('utf-8')
I will use your JSON source data instead of the XML, since that is easier to handle in DataTables.
Here is a basic demo, to start with, followed by some explanatory notes:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.21/css/jquery.dataTables.min.css">
</head>
<body>
<div style="margin: 20px;">
<table id="arxivtable" class="display" style="width:100%">
<thead>
<tr>
<th>title</th>
<th>id</th>
<th>link</th>
<th>author</th>
<th>published</th>
<th>summary</th>
</tr>
</thead>
</table>
</div>
<script type="text/javascript">
$(document).ready(function(){
$('#arxivtable').DataTable({
"ajax": {
url: "YOUR_URL_GOES_HERE",
dataSrc: "feed.entry"
},
"columns": [
{"data": "title"},
{ "data": "id" },
{ "data": "link[].#href" },
{ "data": "author.name" },
{ "data": "published" },
{ "data": "summary" }
]
});
});
</script>
</body>
</html>
Notes
1 - Because you have provided hard-coded HTML column headers, you need to make sure the number of those headers matches the number of columns defined in the DataTable. Alternatively, you can remove the HTML <thead> section and use the DataTables columns.title option.
2 - Your Ajax JSON source data contains an array [ ... ]. DataTables needs to know where this array is located in your JSON response, as part of the Ajax handling option, so that it can iterate over that array. Each element in the array will be used to create a row of HTML table data. The ajax.dataSrc option therefore needs to be set accordingly:
dataSrc: "feed.entry"
Once you have set the above Ajax JSON starting point correctly, then you can use field names for each separate column data value - as shown below.
3 - The author JSON value is actually an object:
"author": {
"name": "Xiaomin Chen"
},
Therefore you need to drill down into that to get the field you want to show in the DataTable:
{ "data": "author.name" },
4 - I removed your column renderer function to keep my initial demo simple, but it can be used to access fields and sub-fields - and concatenate strings and other values as needed (as in your example in the question).
5 - The link JSON value is actually an array of objects. For my basic demo, I just accessed the final entry in that array, and then took the href field:
{ "data": "link[].#href" },
This may not be what you want. You may want to only choose links of a certain type, or choose all links, or something different.
This is where DataTables is limited in what it can handle. It cannot display arbitrary nested JSON values of this type (not surprisingly).
In such cases, you would need to re-structure the JSON, prior to sending it to DataTables - or restructure it in a dataSrc function inside DataTables itself:
"dataSrc": function ( json ) { ...transform and return your JSON here... }
6 - I was not sure what you wanted to display for { "data": "journal" }. I did not see anything called journal in the JSON.
7 - Note that all the source JSON data outside of the feed.entry array is also not available to DataTables. DataTables can only iterate over that outer array. Anything you may also need which is not in that outer array would need to be added to the array, to be accessible to DataTables.
See also Nested object data (arrays) and Nested object data (objects) for more related notes.

extract subset of imported json file in vue's data() function

I have imported the following json file:
[
{
"case_id": "1234",
"thread": [
{
"t_id": "1111",
"text": "test"
},
{
"t_id": "2222",
"text": "test"
}
]
},
{
"case_id": "5678",
"thread": [
{
"t_id": "9999",
"text": "test"
},
{
"t_id": "8888",
"text": "test"
},
{
"t_id": "777",
"text": "test"
}
]
}
]
using the following:
import cases from '../cases.json'
The whole json dataset is available in cases variable and can be used in the template with the support of v-if and v-for.
How can I create a separate dataset (thecase) that contains only threads for a given case_id? In the template I would only like to use v-for to display all threads for a given case_id.
Below is my export default section:
export default {
name: "details",
props: {
case_id: {
required: true,
type: String
}
},
data () {
return {
cases,
thecase: ### THIS IS THE PART I CANNOT FIGURE OUT ###
}
}
};
You can remove thecase from data options and use a computed property instead for thecase. Inside the computed property, we will need to use array .find() method to find the case where case_id is same as the case_id passed in the prop:
data: {
cases,
},
computed: {
thecase: function() {
return this.cases.find(c => c.case_id === (this.case_id || ''))
}
}
and then you can use v-for on thecase.thread just like you would do for a data option like:
<li v-for="item in thecase.thread" :key="item.t_id">
{{ item.text }}
</li>
You can further modify it and use v-if & v-else to show a text like No cases were found with give case id in case there is no match found.

how to read json file that can have different entries

I have json files where the various fields can change.
"eventHistory": [{
"occurredAt": "2018-03-17T10:40:05.707 0000",
"calluuid": "G8EMGR6EKD7DLDRP79FOEONVQ4000031",
"eventId": "2018-03-17T10:40:05.707Z_1521283205_G8EMGR6EKD7DLDRP79FOEONVQ4000031",
"event": "Data",
"data": {
"added": {
"OtherTrunkName": "sbc-trunk",
"OriginationDN": "1234",
"BusinessCall": "0",
"OriginationDN_location": "MNLSwitch"
}
}
}, {
"occurredAt": "2018-03-17T10:40:06.033 0000",
"calluuid": "G8EMGR6EKD7DLDRP79FOEONVQ4000031",
"eventId": "2018-03-17T10:40:06.033Z_1521283206_G8EMGR6EKD7DLDRP79FOEONVQ4000031",
"event": "Data",
"data": {
"added": {
"IW_CaseUid": "04d575ba-32e3-48da-8986-a19a6ff493b3",
"IW_BundleUid": "bf3ac19e-e2ea-4d7b-9b48-ef5e17dfdaa1"
}
}
}, {
"occurredAt": "2018-03-17T10:40:10.407 0000",
"calluuid": "G8EMGR6EKD7DLDRP79FOEONVQ4000031",
"eventId": "2018-03-17T10:40:10.407Z_1521283210_G8EMGR6EKD7DLDRP79FOEONVQ4000031",
"event": "Data",
"data": {
"added": {
"WrapUpTime": "0"
},
"deleted": {
"OriginationDN_location": "MNLSwitch",
"OriginationDN": "1234"
}
}
},
Is there an 'easy' way to simply read through and add these as variables to a js. I gather each field and then post to PHP which saves.
then I need to display in a tree... another question likely to appear on that one too.
Any help much appreciated.
It is unclear if you require the variables to be declared in a JS file, or if you simply want each key and value pair to be extracted out. Thus, I've provided 2 approaches that could be useful for your use case.
Suggestion: If it doesn't matter, why not handle the whole JSON object in your PHP save script?
1. Extract Key Value pairs
If you require a recursive JS function to get all the keys and values, you may use this function, referenced from here.
Caveat: This function ignores keys with nested key value pairs, so you will need to modify it to suit your needs.
var json = `
{
"occurredAt": "2018-03-17T10:40:06.033 0000",
"calluuid": "G8EMGR6EKD7DLDRP79FOEONVQ4000031",
"eventId": "2018-03-17T10:40:06.033Z_1521283206_G8EMGR6EKD7DLDRP79FOEONVQ4000031",
"event": "Data",
"data": {
"added": {
"IW_CaseUid": "04d575ba-32e3-48da-8986-a19a6ff493b3",
"IW_BundleUid": "bf3ac19e-e2ea-4d7b-9b48-ef5e17dfdaa1"
}
}
}
`
var obj = JSON.parse(json);
iterate(obj,'');
function iterate(obj, stack) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
iterate(obj[property], stack + '.' + property);
} else {
console.log(property + " " + obj[property]); //get key and value pair here
}
}
}
}
2. Declare Variables in JS
If you require variables to be declared in a JS <script> tag, you can consider using PHP to generate a HTML page with the variables declared.
The code below produces a html file with the following script tag:
Advantage: This approach allows you to get creative, and generate a lot of JS code that is variable specific.
Disadvantage: Debugging this could be hard.
Caveat: The example below is not recursive, so you'll have to do that on your own.
<?php
$string = <<<EOD
{
"occurredAt": "2018-03-17T10:40:06.033 0000",
"calluuid": "G8EMGR6EKD7DLDRP79FOEONVQ4000031",
"eventId": "2018-03-17T10:40:06.033Z_1521283206_G8EMGR6EKD7DLDRP79FOEONVQ4000031",
"event": "Data",
"data": {
"added": {
"IW_CaseUid": "04d575ba-32e3-48da-8986-a19a6ff493b3",
"IW_BundleUid": "bf3ac19e-e2ea-4d7b-9b48-ef5e17dfdaa1"
}
}
}
EOD;
$json = json_decode($string);
echo '<script>';
foreach($json as $key => $value)
{
$output = <<<EOD
var $key = "$value";
EOD;
echo $output;
}
echo '</script>';
?>

Knockout nested model properties

I need a feature like a repeating table in my web form and need to store my data in a JSON format like this:
[
{
"id": 1, "name": "T01", "title": "T01 form title", "totalPoints": "total of all points for sections below",
"sections":
[
{ "section": "First section", "point": 4 },
{ "section": "Second section", "point": 5 }
]
},
{
"id": 2, "name": "T02", "title": "T02 form title", "totalPoints": "total of all points for sections below",
"sections":
[
{ "section": "First section", "point": 4 },
{ "section": "Second section", "point": 5 }
]
}
]
I'm using knockout and I implemented top level of the structure below, but struggling with a nested sections.
Here is my attempts to structure my Model, please advise what option to use, or if this incorrect, please advise the right option:
function Form(data)
{
this.Id = ko.observable(data.Id);
this.Name = ko.observable(data.Name);
this.Title = ko.observable(data.Title);
this.Total = ko.observable(data.Total);
// Option 1, like an array
this.Sections = ko.observableArray([
{
Section: data.Section,
Point: data.Total
}
]);
// Option 2, like a function
function Sections(data) {
this.Section = ko.observable(data.Section),
this.Point = ko.observable(data.Point)
}
}
Later I push this data as a model to observable array like this, again I can push the top level, but couldn't nested properties:
self.addForm = function () {
self.forms.push(
new Form({
Id: this.id(),
Name: this.name(),
Title: this.title(),
Total: function() // TODO
// Sections nested properties implementation
})
);
self.name("");
};
I'd say it's best to define two viewmodels:
Form, and
Section
Your Form will have three kinds of properties:
Regular ko.observable values, for Id, Name and Title
Note: If some of those are static, it's best to not make them observable. I can imagine the Id will never change: you can signal this to other readers of your code by making it a regular string.
An observableArray for your list of Sections
A pureComputed for Total that sums up all your Section points
function Section(points, title) {
// TODO: check if you need these to be observable
this.points = points;
this.title = title;
};
// Create a new Section from a plain object
Section.fromData = function(data) {
return new Section(data.point, data.section);
};
function Form(id, name, title, sections) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.title = ko.observable(title);
// Map using our static constructor helper
this.sections = ko.observableArray(
sections.map(Section.fromData)
);
this.total = ko.pureComputed(function() {
return this.sections().reduce(function(sum, section) {
return sum + section.points;
}, 0);
}, this);
}
// A helper to get from your data object to a new VM
Form.fromData = function(data) {
return new Form(data.id, data.name, data.title, data.sections);
}
// Apply bindings
ko.applyBindings({
forms: getTestData().map(Form.fromData)
});
// Your test data
function getTestData() {
return [{
"id": 1,
"name": "T01",
"title": "T01 form title",
"totalPoints": "total of all points for sections below",
"sections": [{
"section": "First section",
"point": 4
},
{
"section": "Second section",
"point": 5
}
]
},
{
"id": 2,
"name": "T02",
"title": "T02 form title",
"totalPoints": "total of all points for sections below",
"sections": [{
"section": "First section",
"point": 4
},
{
"section": "Second section",
"point": 5
}
]
}
]
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<ul data-bind="foreach: forms">
<li>
<p data-bind="text: title"></p>
<ul data-bind="foreach: sections">
<li>
<span data-bind="text: title"></span> (
<span data-bind="text: points"></span>)
</li>
</ul>
<span data-bind="text: 'total: (' + total()"></span>)
</li>
</ul>
The Section class is a bit plain; if you don't have additional requirements you could choose to use plain objects instead.

How do I use knockout to bind nested foreach loops on dynamically added properties?

I have a collection of items. The items are broken down into "types" and then further divided within the type into "categories". I do not know the names of the "types" or the "categories" before hand.
I would like to do some nested foreach binding to represent the data hierarchically. Something like this:
<ul data-bind="foreach: OrderItems.Types">
<li>
ItemType: <span data-bind='text: $data'></span>
<ul data-bind="foreach: Categories">
<li>
Category: <span data-bind='text: $data'></span>
<ul data-bind="foreach: OrderItems">
<li>
Item: <span data-bind="text: Name"> </span>
</li>
</ul>
</li>
</ul>
</li>
var order = {
"OrderNumber": "394857",
"OrderItems": {
"Types": {
"Services": {
"Categories": {
"carpet cleaning": {
"OrderItems": [
{
"OrderItemID": "9d398f88-892c-11e3-8f31-18037335d26a",
"Name": "ARug-Oriental Rugs (estimate on site)"
},
{
"OrderItemID": "9d398f53-892c-11e3-8f31-18037335d26a",
"Name": "C1-Basic Cleaning (per room)"
},
{
"OrderItemID": "9d398f54-892c-11e3-8f31-18037335d26a",
"Name": "C2-Clean & Protect (per room)"
},
{
"OrderItemID": "9d398f55-892c-11e3-8f31-18037335d26a",
"Name": "C3-Healthy Home Package (per room)"
}
]
},
"specialty": {
"OrderItems": [
{
"OrderItemID": "9d398f8f-892c-11e3-8f31-18037335d26a",
"Name": "SOTHR-Other"
}
]
},
"tile & stone": {
"OrderItems": [
{
"OrderItemID": "9d398f8e-892c-11e3-8f31-18037335d26a",
"Name": "TILE-Tile & Stone Care"
}
]
},
"upholstery": {
"OrderItems": [
{
"OrderItemID": "9d398f7b-892c-11e3-8f31-18037335d26a",
"Name": "U3S1-Upholstery - Sofa (Seats 3: 7 linear feet)"
},
{
"OrderItemID": "9d398f7c-892c-11e3-8f31-18037335d26a",
"Name": "U3S2-Upholstery - Sofa - Clean & Protect (Seats 3: 7 linear feet"
}
]
}
}
},
"Products": {
"Categories": {
"carpet cleaning": {
"OrderItems": [
{
"OrderItemID": "9d398f84-892c-11e3-8f31-18037335d26a",
"Name": "PLB-Leave Behind Item"
}
]
}
}
}
}
}
};
var viewModel = ko.mapping.fromJS(order);
ko.applyBindings(viewModel);
here's a fiddle with the above code: http://jsfiddle.net/mattlokk/6Q5f7/5/
To bind against your structure, you would need to turn the objects into arrays. Given that you are using the mapping plugin, the easiest way would likely be to use a binding that translates an object with properties to an array of key/values.
Here is a sample binding:
ko.bindingHandlers.objectForEach = {
init: function(element, valueAccessor, allBindings, data, context) {
var mapped = ko.computed({
read: function() {
var object = ko.unwrap(valueAccessor()),
result = [];
ko.utils.objectForEach(object, function(key, value) {
var item = {
key: key,
value: value
};
result.push(item);
});
return result;
},
disposeWhenNodeIsRemoved: element
});
//apply the foreach bindings with the mapped values
ko.applyBindingsToNode(element, { foreach: mapped }, context);
return { controlsDescendantBindings: true };
}
};
This will create a computed on-the-fly that maps the object to an array of key/values. Now you can use objectForEach instead of foreach against your objects.
Here is a basic sample: http://jsfiddle.net/rniemeyer/nn3jg/ and here is an example with your fiddle: http://jsfiddle.net/rniemeyer/47Wbe/