Knockout nested components: $(document).ready() …

2019-09-12 17:19发布

问题:

So I have several nested knockout components:

<component1>
   <component2>
   ....
   </component2>
</component1>

component1 is my own component, written by me, but component2 is a third party code and I am not supposed to change it. Here is the problem:

In $(document).ready(), I use jquery $('button').click(...) to assign click even handler to, say, all buttons, for simplicity. The result it, only buttons inside component1 and outside component2 get the handler, and none of the button inside component2 has the handler (nothing happens when I click on them).

Note that component2 relies on (consumes) data from some ajax call, so this may delay its loading. Because ajax is async, so it's possible that $(document).ready() runs even before the ajax finishes, and therefore, the $('button').click(...) didn't catch the buttons inside components as they have not been rendered yet.

Additional problem: The viewmodel1 of component1 seems to always be empty when inside component2. The context is correct; it's just empty (for example: an array of the viewmodel1 is empty inside component2, not not empty when outside component2.

How to make all the buttons inside component2 get the handler?

回答1:

If you can't modify component2 and it doesn't provide you with a way to know when it's done loading then your only option is to loop until it's done loading. You can tell when it's done loading by testing for some class/id/etc that you know is inside the component.

<component1>
   <component2>
       ...
       <div class="some-class-I-know-will-be-here"></div>
   </component2>
</component1>

var loop = window.setInterval(function () {
    if ($('.some-class-I-know-will-be-here')[0]) {
        componentHasLoaded();
        window.clearInterval(loop);
    }
}, 100);

Note that this is indeed a hack/workaround. You should probably be passing an "onClick" function into the component which would wire it up appropriately.

[Edit]

This workaround is overkill for simply attaching click events. The other answers cover how this can be accomplished using jQuery's event delegation. This workaround is only practical when you need to modify the component's template/viewModel after it's been created and you have no way to modify the component any other way.



回答2:

You should use event delegation, so that the DOM elements don't have to be present at the time you set up the trigger.

$(document).on('click', 'button', ...);


回答3:

Try to use the jQuery feature of adding events handlers to any future element inside. Check the syntax on the code below.

$(document).on("click", "div", function(){
    console.log(this.id);
});

Even if some div is not loaded yet, the click event will work on those elements.



回答4:

If the jQuery delegate event doesn't fit your scenario and you are using Knockout >= 3.5.0, you have the childrenComplete binding parameter you can use on your component.

I use it like that:

<script>
        window.componentReady= false;
        function onAccordionReady() {
            window.componentReady = true;
        }
</script>
<div data-bind="component: {name: 'my-component', childrenComplete: onComponentReady">
</div>

And on javascript, similar to CrimsonChris's answer but perhaps more performant:

<script>
$(document).ready(function documentReady() { //Notice the named function

    if (!window.componentLoaded) {
        setTimeout(documentReady, 1000);
        return; //Don't forget the return!
    }

    //... All the code I want to execute on document ready that
    //dependent on the components having finished rendering

}
</script>