Something like this (Live copy on js fiddle):
CSS:
.left {
float: left;
}
.clear {
clear: both;
}?
HTML:
<p>Current:
<a href="#" data-bind="visible: (stack.length > 0), text: selectedNode().name, click: selectParentNode"></a>
<span data-bind="visible: (stack.length <= 0), text: selectedNode().name"></span>
</p>
<p class="left">Children: </p>
<ul class="left" data-bind="template: {name: 'childList', foreach: selectedNode().children}"></ul>
<script type="text/html" id="childList">
<li data-bind="click: function(){nodeViewModel.selectChildNode($data)}">
<a href="#">A${name}</a>
</li>
</script>
<br /><br />
<ul class="clear" data-bind="template: {name: 'backBtn'}"></ul>
<script type="text/html" id="backBtn">
<a href="#" data-bind="visible: $data.selectedNode().back, click: function() { nodeViewModel.selectBackNode($data.selectedNode().back) }">Back</a>
</script>?
JavaScript:
var node = function(config, parent) {
this.parent = parent;
var _this = this;
var mappingOptions = {
children: {
create: function(args) {
return new node(args.data, _this);
}
}
};
ko.mapping.fromJS(config, mappingOptions, this);
};
var myModel = {
node: {
name: "Root",
children: [
{
name: "Child 1",
back: 1,
children: [
{
name: "Child 1_1",
back: 1,
children: [
{
name: "Child 1_1_1",
back: 4,
children: [
]},
{
name: "Child 1_1_2",
back: 2,
children: [
]},
{
name: "Child 1_1_3",
back: 1,
children: [
]}
]}
]},
{
name: "Child 2",
back: 1,
children: [
{
name: "Child 2_1",
back: 1,
children: [
]},
{
name: "Child 2_2",
back: 1,
children: [
]}
]}
]
}
};
var viewModel = {
nodeData: new node(myModel.node, undefined),
selectedNode: ko.observable(myModel.node),
stack: [],
selectBackNode: function(numBack) {
if (this.stack.length >= numBack) {
for (var i = 0; i < numBack - 1; i++) {
this.stack.pop();
}
}
else {
for (var i = 0; i < this.stack.length; i++) {
this.stack.pop();
}
}
this.selectNode( this.stack.pop() );
},
selectParentNode: function() {
if (this.stack.length > 0) {
this.selectNode( this.stack.pop() );
}
},
selectChildNode: function(node) {
this.stack.push(this.selectedNode());
this.selectNode(node);
},
selectNode: function(node) {
this.selectedNode(node);
}
};
window.nodeViewModel = viewModel;
ko.applyBindings(viewModel);?
This sample just maps an infinitely nested set of JSON data, and I can say from actually using this exact code in application that is works great.
Some of the extra functions like
selectBackNode and selectParentNode
allow you to move back up the tree.
While navigating the example the parent label becomes a link to allow for going up one level, and some of the leaf nodes have a back button that allows them to move back up the tree by a given number of levels.
--EDIT--
If your leaf nodes don't have a children array you might get a problem where additional data is introduced that doesn't exist in the model.