Is there a way to configure a Knockout component to replace the container element instead of nesting its content inside the container element?
For example, if I have a custom component registered as my-custom-element
with the following template:
<tr>
<p>Hello world!</p>
</tr>
Is it possible to use the component like this:
<table>
<tbody>
<my-custom-element></my-custom-element>
</tbody>
</table>
And have the final product be this:
<table>
<tbody>
<tr>
<p>Hello world!</p>
</tr>
</tbody>
</table>
Instead of this: (the way Knockout renders components by default)
<table>
<tbody>
<my-custom-element>
<tr>
<p>Hello world!</p>
</tr>
</my-custom-element>
</tbody>
</table>
Based on the answer to this question, it seems that this functionality is built into the templating engine, which I'm assuming is also used when rendering component templates.
Is there a way to specify that a component should be rendered with a renderMode
of replaceNode
?
I'm aware of the "virtual element" syntax, which allows components to be defined inside an HTML comment:
<table>
<tbody>
<!--ko component { name: 'my-custom-element' }--><!--/ko-->
</tbody>
</table>
but I really dislike this syntax - writing real code inside a comment feels like a dirty, dirty hack.
I imagine the lack of this option could be defended as such, considering the nature of the library (there, I said it), and the developer's team philosophy:
Knockout is a library and unlike other MVC's it does not force you to use a framework-defined way to structure your application. If you consider the template engine in Knockout vs. virtually all other JS templating engines (in Angular, underscore, mustache, etc.), Knockout comes out as the only one not 'modding' native HTML5 rendering. All others use a custom tag, be it <% %>
or {{ }}
which requires a small JS parser to transform the tags into something meaningful (now KO also has a Knockout punches plugin which includes the mustache-style tags, and admittedly KO does 'sin' a small bit with <!-- ko -->
comments). KO instead uses HTML 5 custom elements, comment and attribute tags, completely "vanilla".
For instance, the JS/DOM type of 'object (and real life?) hierarchy' is used: only parents can ever exerce power on their children, and so the bound-to element is not replaced, but enlarged with children. For illustration:
// we cannot do this in JS
document.getElementById('elem').remove(); //implied .remove(self)
// instead we do this
var elem = document.getElementById('elem');
container = elem.parentNode.removeChild(elem);
Subsequently, the preferred way of data-binding with KO, illustrated well by the foreach
binding, is:
<div data-bind="foreach: myList">
<span data-bind="text: $data"></span>
</div>
The previous snippet being an HTML representation of a JS array, the hierarchy is visible again:
var myArr = [1,2,3,4,5];
// we cannot do the following without reference to the array index,
// which is not held by the object itself,
// but a meta-property getting meaning relative to the parent
myArr[0].splice(0,1); //remove
This leads to your HTML view being a perfect replication of your JS data (and it would be interesting to see someone build a tool that shows the data-bind indentation levels (with with
and foreach
) in an HTML document. In some cases however, you need comment tags, so as to not break the lay-out of your HTML, or your css rules (nesting), for example a 'text-only' component (i18n) that is to be injected between text nodes:
<p>Some predefined text with
<!-- ko text: 'some variable text' --><!-- /ko -->
and more predefined text</p>
Or when you don't want an empty element to take up space when hidden
<!-- ko if: !hidden() --><div id="myToggleableDiv"></div><!-- /ko -->
And then there are custom tags, which are standardized and a hell of a lot clearer; but unfortunately not a 100% ready yet. Choose data-bind
first, <!-- ko -->
second, and <custom>
third (would be higher if fully implemented). That's how I see it, anyway. As for your specific case, if your component holds a list model, you could do:
<table data-bind="{component: {name: 'custom', params {..}}"></table>
And include the tbody
in your VM, else if it is a listitem model, you can use either one of the three 'syntaxes', eg the comment syntax
<table>
<tbody data-bind="foreach: mylist">
<!-- ko component: {name: 'custom', params: $data} --><!-- /ko -->
</tbody>
</table>
Or decouple your component completely from the requirement of being nested inside a specific parent (table), adhering to the SOC principle, eg:
<table>
<tbody data-bind="foreach: mylist">
<tr data-bind="foreach: properties">
<td data-bind="component: {name: 'custom', params: $data}></td>
</tr>
</tbody>
</table>
or with a custom tag:
<table>
<tbody data-bind="foreach: mylist">
<tr data-bind="foreach: properties">
<td><custom params= "{data: myData"></custom></td>
</tr>
</tbody>
</table>
in that order of preference..