Custom component in HTML binding

99 views
Skip to first unread message

robine...@gmail.com

unread,
Feb 20, 2015, 1:35:52 AM2/20/15
to knock...@googlegroups.com
tdlr: Is there a way to use custom components inside an HTML binding? 

Some background
The overall goal here is to create a custom component, view_switcher, that displays a different child components depending on the URL. My first solution used hardcoded indexing and a series of if: bindings, but that way is fragile and I'd like something more generic. Swapping out the actual component strikes me as cleaner than listing everything and "hiding" the irrelevant ones. 

So, we want <view_switcher> to dynamically include <view_a> (or <view_b>, etc). 

View A
ko.components.register('view_a', {
   
template: 'This text should show up.',
    viewModel
: function () {
   
}
});

Attempt 1
Use a component: binding on a child div to dynamically set the bound component. This almost does what's needed, but misses one part: I rather like using the custom component names in CSS selectors. 

<!-- Doesn't work with CSS selecting on "view_a", since it's actually a div. -->
 <view_switcher>
   
<div data-bind="component: 'view_a'"></div>
 
</view_switcher>

Attempt 2
Use an html: binding to actually use the appropriate component tag inside the parent. 

ko.components.register('view_switcher', {
    template: '<div data-bind="html: content()"></div>',
    viewModel: function () {
        var self = this;

        self.content = function () {
            return '<view_a></view_a>';
        };
    }
});

But the result is this:

 <view_switcher>
   
<view_a></view_a>
 
</view_switcher>

Not the expected:

 <view_switcher>
   
<view_a>This text should show up.</view_a>
 
</view_switcher>

It seems that the result of an HTML binding doesn't get noticed by knockout. 
  1. Is there a way to tell knockout to "parse" the html binding result? 
  2. If not, is there a reasonable way to accomplish this? I'm still a newbie with these "meta" type bindings. I looked at template binding, but it doesn't seem to do what I'm looking for.
Worst case, I can revert to attempt 1 and use IDs for the CSS. It just seems like using components within HTML bindings should work (since they work within the normal custom component templates.)

Tried under both version 3.2.0 and 3.3.0.

PS. Thanks for all the work on this fantastic library, and custom components especially. They've made web UI building so, so, so much easier. 



robine...@gmail.com

unread,
Mar 2, 2015, 8:55:29 PM3/2/15
to knock...@googlegroups.com, robine...@gmail.com
After a conversation with Ryan of knockmeout.net and a peek at the HTML binding handler, I have a solution. However, it might be terrible security, though I can't think of how quite yet.

The default HTML handler is:

ko.bindingHandlers['html'] = {
               
'init': function() {
                   
// Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
                   
return { 'controlsDescendantBindings': true };
               
},
               
'update': function (element, valueAccessor) {
                   
// setHtml will unwrap the value if needed
                    ko
.utils.setHtml(element, valueAccessor());
               
}
           
};

I created a dynamicHtml  handler that leaves out the init portion, and it works.

But now I have this question: should I be using this handler? What are the security implications? I can't think of how allowing binding on injected nodes could be any more harmful than the default HTML handler.

What am I missing, if anything?

This is my handler:

ko.bindingHandlers.dynamicHtml = {
    update
: function (element, valueAccessor) {
        ko
.utils.setHtml(element, valueAccessor());
   
}
};

Reply all
Reply to author
Forward
0 new messages