3
votes

This is a question to clarify the best method (or any method!) for achieving this. It may be the way I'm trying to apply my .net coding experience to this new way of doing things but I cannot believe this is not a real world problem for many coders.

So. Master view/viewmodel uses many sub views on the page as certain sections change depending on user choices/actions. These sub views are composed into the master page:

<div data-bind="compose: { model: articleSection, preserveContext: true }"></div>

...and within the sub view I am able to refer to root observables that control the master view like so:

<a href="#" class="btn" data-bind="css: { 'btn btn-default': true, 'btn-primary': $root.inEditMode() }, click: $root.setEditMode, enable: $root.articleSelected()">Edit</a>

However, one of the sub views itself has a sub sub view which uses a custom binding and a template for construction as it's a treeview and needs to be able to recursively construct the layout.

The sub view code is just:

<div id="fancytree" data-bind="groupTree: treeGroups">
    <!--<ul data-bind="template: { name: 'itemTmpl', foreach: treeGroups }, groupTree: {}"></ul>/-->
</div>

The template is just:

    <script id="itemTmpl" type="text/html">
      <!-- Template used in labtool for tree-->
        <li>
            <span>
                <span data-bind="text: name" />
            </span>
            <ul data-bind="template: { name: 'itemTmpl', foreach: children }"></ul>
        </li>
    </script>  

The custom binding is:

ko.bindingHandlers.groupTree = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var tm = valueAccessor();
        var tmUnwrapped = tm();
        $(element).fancytree({
            minExpandLevel: 1,
            source: tmUnwrapped,
            lazyload: function (e, data) {
                data.result = datacontext.getGroupChildren('1111');
            },
            activate: function (event, data) {
                var node = data.node;
                var tg = rootVm.selectedGroupId();
                alert(node.title);
            },
        })
    }

So my problem is that I cannot get at what I think of as a "global variable" from my .net days which is an observable in my top-level root viewmodel called "selectedArticle". When a user clicks on the tree view node, the "activate" function is fired, the alert pops up with the correct details and I simply want to be able to put that value into the observable in my root viewmodel which is, effectively, two levels up from the binding context of the custom binding handler.

This HAS to be something that other people do, surely? I've googled until I'm blue in the face but I'm obviously missing the keywords or concepts I need to get the hits I need. In pseudo-code in the custom binding "activate" function I simply want to be able to type in something like "root.selectedArticle = node.title" but obviously root is not available in that manner, so that's where I come unstuck!

2
I cannot try this out now but bindingContext.$root should be your root viewmodel...nemesv
Nope, sorry. var rootVm = bindingContext.$root just results in rootVm containing the viewmodel for the current view: moduleId: "viewmodels/groupTree"TheMook
Have you used preserveContext: true in all your compose binding until the top?nemesv
I can't. I show the exact binding I use in my sub-sub view: <!--<ul data-bind="template: { name: 'itemTmpl', foreach: treeGroups }, groupTree: {}"></ul>/--> and there's no way to do a compose binding on that!TheMook
The template is a red herring. I found out I didn't even need it.TheMook

2 Answers

14
votes

You can access to it from binding context (5th parameter of init/update functions):

init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) 
 {  
    bindingContext.$data          // current data
    bindingContext.$parent        // parent binding  
    bindingContext.$parents[0]    // same to bindingContext.$parent  
    bindingContext.$parents[1]    // parent of parent  
    bindingContext.$parentContext // same with bindingContext but from parent
    bindingContext.$root          // you root View Model  
 // ...  
 }
1
votes

You can subscribe to an event at the root viewmodel and fire that event from the binding handler when the article is selected.

http://durandaljs.com/documentation/Leveraging-Publish-Subscribe.html