Justin Howard
Justin Howard

Reputation: 5643

knockout.js - Apply custom binding after components are bound

I am using a custom menu binding for the jquery ui menu widget. Inside the HTML for my menu, I am using a component. It looks something like this:

<ul data-bind="menu: {...}">
    <!-- ko foreach: menuComponents -->
    <li>
        <div data-bind="component: $data"></div>
    </li>
    <!-- /ko -->
</ul>

My custom binding is using applyBindingsToDescendents() to resolve the bindings inside the ul before initializing the menu. This is a simplified version of my binding.

ko.bindingHandlers.menu = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        ko.applyBindingsToDescendants(bindingContext, element);

        $(element).menu();

        return {controlsDescendantBindings: true};
    }
};

The problem is that components are loaded asynchronously. I am using a custom component loader, but I'm not sure if that matters. The side effect is that the component binding isn't resolved until the component is loaded. So the load order looks like this:

  1. Menu binding initialization is started
  2. Descendant bindings are applied by applyBindingsToDescendents
  3. jQuery menu widget is initialized
  4. Component is initialized and bound asynchronously

Since the component isn't initialized until after bindings are applied, my menu isn't rendering properly.

Is there any way I can force component descendant bindings to be applied synchronously? Or is there a way I can detect when they are applied and refresh the menu widget?

Upvotes: 1

Views: 613

Answers (1)

Justin Howard
Justin Howard

Reputation: 5643

My working solution at the moment is to avoid the use of components completely. I simply use the template binding and apply the view models manually.

<ul data-bind="menu: {...}">
    <!-- ko foreach: menuItems -->
    <li>
        <div data-bind="template: {name: $data.template, data: $data.data}"></div>
    </li>
    <!-- /ko -->
</ul>

I tried using the synchronous option for my components, but the problem is that it does not gurantee synchronous operation. Another solution was to bind my menu in a setTimeout call, but I avoided this solution because it can potentially cause other issues. But that would have looked like this:

ko.bindingHandlers.menu = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        ko.applyBindingsToDescendants(bindingContext, element);

        setTimeout(function() {
            $(element).menu();
        });

        return {controlsDescendantBindings: true};
    }
};

Upvotes: 0

Related Questions