I've been using Knockoutjs for a while, but there is something I haven't been able to solve and I'm sure there must be an easy way to do it.
I really like nesting objects, using "foreach" on the html and then accessing each of these object's property directly. That way it keeps the code simple and clear.
The problem is that sometimes I use an observableArray to hold only one element in order to use the "foreach way" that I mention.
Is there other way of saying "I'm now within this scope", same behaviour as "foreach".
Example: here
<body>
<!-- ko foreach: people -->
<div data-bind="text: name">name</div>
<!-- /ko -->
<br />
<!-- here I would like to say I'm inside 'importantPerson' and therefore name is a property of importantPerson -->
<!-- <div data-bind="text: name">name (important)</div> -->
<br />
</body>
You are looking for the with binding:
The with binding creates a new binding context, so that descendant elements are bound in the context of a specified object.
So you code would look like this:
<body>
<!-- ko foreach: people -->
<div data-bind="text: name">name</div>
<!-- /ko -->
<br />
<!-- ko with: importantPerson -->
<div data-bind="text: name"></div>
<!-- /ko -->
<br />
</body>
Demo JSFiddle.
Related
I have a javascript object graph, an HTML form, and knockout bindings connecting the two. The form is complex, and sometimes the form needs to add some computed observables to some sub-object in our object graph, and I want to do that locally in the the HTML element that has the data-bind which relies on this, I don't want such knowledge somewhere in some global script.
<div class="widget" data-bind="foreach: subThing">
<script type="text/javascript">
$data._scratchpad = ko.computedObservable( ... );
</script>
...
<input data-bind="value: _scratchpad"/>
...
</div>
Now in the context of this script, the binding context is of course not yet set up, so the $data property is not yet available.
But is there some event that I might put on the element or something so I can catch when the bindings are first initialized so I can add the necessary things before the actual data-bind expressions want to refer to them?
I came up with a solution which is just a little ugly, but actually practically correct. Instead of this script element above, I just use a virtual element that contains nothing and whose only point is to get an if: condition evaluated, where then we put the statements into the body of a function that gets evaluated:
<div class="widget" data-bind="foreach: subThing">
<!-- ko if: (function() { if(!$data._scratchpad) {
$data._scratchpad = ko.computedObservable( ... );
}})() --> <!-- /ko -->
...
<input data-bind="value: _scratchpad"/>
...
</div>
The nice thing is that required no modification of the source code. And while it is just a little ugly with the boiler-plate code:
<!-- ko if: (function() { if(!...) {
...
}})() --> <!-- /ko -->
I could potentially use a custom binding's preprocessor to wrap this function around and say instead simply:
<!-- ko setup:
...
--> <!-- /ko -->
this is almost neat, but really not so much better that it's worth it.
It's kind-a handy that this virtual element definition is already in a comment, so there won't be any worries with the javascript code using special characters.
I'm trying to create a simple component in Knockout (3.3.0):
ko.components.register('test', {
viewModel: function() {
this.test = 'hello'
},
template:
`<span data-bind='if: 1, text: test'></span>`
});
ko.applyBindings();
See fiddle.
Now when I instantiate a <test></test> somewhere else, I get an error:
Multiple bindings (if and text) are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.
Is this really not possible? This would be like the most basic functionality to incorporate if you ask me. I know I could use <!-- ko text --> but what about setting other attributes like src and using if at the same time?
OK I found the (or at least, one possible) solution: Use <!-- ko if --><!-- /ko -->. This way, the template can be written like
<!-- ko if: 1-->
<span data-bind='text: test'></span>
<!-- /ko -->
I still don't think it's perfect, in Vue I would just do <span v-if='1'>{{text}}</span> bam done, but I guess not everything in this world can be as awesome as Vue...
I want to use variable in html find but in can't find any way to do it. I tried this but not working for me.
<!--ko foreach: { data: test(), as: 'method'}-->
<!--ko var isValid = test_title;
/ko !-->
<tr class="row"><th colspan="4" data-bind="text: isValid" class="test_title"></th></tr>
<!-- /ko !-->
First Experience with KO.
You can't write javascript in a ko comment, but you can write it in the binding value.
In Knockout 2.x, one could write:
<div data-bind="template: { name: 'my-template', foreach: elements }">
<div class="placeholder"><- these are the array elements.</div>
</div>
This way, a placeholder element could be appended to the rendered elements.
But, as I see, Knockout 3.0 changed this behavior: "placeholder" markup is just wiped out of the container element.
Is this intentional? Any workarounds?
One solution that I can immediately think of is adding "afterRender" to the template definition, but this has (according to my experiments with KO 3.0) a weird glitch: it doesn't fire for empty "elements" array.
Thank you in advance for your answers.
In comparing similar usages of templates to that of using the foreach option, I could not get the placeholder markup to render except for the specific case identified. As such, I'd assume that might not have been an intended usage.
Since the foreach option to the template binding doesn't provide anything that can't be replicated, one option would be to move the elements into the data option of a regular template and then "manually" provide the foreach markup either within a "container" element or containerless.
Containerless option
<script type="text/html" id="containerless-regular-template">
<!-- ko foreach: $data -->
<div data-bind="text:$data"></div>
<!-- /ko -->
<div class="placeholder"><- these are the array elements.</div>
</script>
If you want to get a little more fancy due to more dynamic requirements for the "placeholder", you could nest another template call at the end of the foreach markup within a containerless if binding. The outermost container would be limited but the contents would be dynamic.
<script type="text/html" id="my-nested-template">
<!-- ko foreach: $data.foreach -->
<div data-bind="text:$data"></div>
<!-- /ko -->
<!-- ko if: $data.template -->
<div data-bind="template: { name: template.name, data: template.data }"></div>
<!--/ko -->
</script>
<div data-bind="template: { name: 'my-nested-template', data: {foreach: [], template: {name: 'another-template-name', data: {}}} }">
Fiddle
I have an knockout observable array of activities which contains audits and comments. I've got the data from the server and sorted the array of activities based on the timestamp of the objects.
I'd like to be able to conditionally display html based on the type, so audits and comments will look different.
<!-- ko foreach: activities -->
<div class="audit" data-bind="visible: {activity is typeof Audit}">
#*Do some audit html*#
</div>
<div class="comment" data-bind="visible: {activity is typeof Comment}">
#*Do some comment html*#
</div>
<!-- /ko -->
I've got the following html but I don't know how do the condition, I just wrote something in above as a placeholder so you get the idea of what I'm trying to achieve.
I'm probably approaching this all wrong, any help much appreciated!
Nayjest's solution should work if you change the visible binding to an if binding - that way it won't try render the parts with the title dependency.
A better solution, however, is probably to have two templates and execute them based on the type. You could have a method on the VM that takes $data and returns, for example, 'auditTemplate' or 'commentTemplate' depending on the result of something like $data instanceof Audit. You would then have two templates embedded as script tags with those ids:
<script id="auditTemplate" type="text/html">
<div class="audit">
<!-- Do some audit stuff -->
</div>
</script>
<script id="commentTemplate" type="text/html">
<div class="comment">
<!-- Do some comment stuff -->
</div>
</script>
And then in your VM, you'd have something like:
this.getTemplate = function(data) {
return (data instanceof Audit) ? 'auditTemplate' : 'commentTemplate'
}
In your page's html you'd do something like:
<!-- ko foreach: activities -->
<div databind="template: {name:$parent.getTemplate($data), data: $data}"></div>
<!-- /ko -->
If you have class Audit that is visible in global scope and property 'activities' of view model, try something like this:
<div data-bind="foreach: activities">
<div data-bind="visible: $data instanceof Audit">
<h1 data-bind="text: $data.title"></h1>
<!-- Some other data here -->
</div>
</div>