Knockout.js: make id based on text binding - javascript

I want to have something like following code:
<!-- ko foreach: subTopics -->
<div id='subtopic-name-here'>
<!-- /ko -->
That is I want to have id of my div as the name of subtopic(data-bind="text:name").How can I do that ?

You should be able to use the attr binding for this:
<!-- ko foreach: subTopics -->
<div data-bind="attr: {'id': name}"></div>
<!-- /ko -->
http://knockoutjs.com/documentation/attr-binding.html

Related

Is it possible to have knockout call a function when a view is rendered?

I have this relatively simple .cshtml view with knockout:
<div class="selected-cluster" data-bind="visible: Selected()">
<div data-bind="foreach: {data: Topics(), as: 'topic'}" class="row">
<!-- ko if: topic.Grid && topic.FirstIterative -->
#Html.Partial("Partial/_TopicGrid")
<!-- /ko --><!--END if: topic.Grid && topic.FirstIterative -->
<!-- ko ifnot: topic.Grid -->
#Html.Partial("Partial/_Topic")
<!-- /ko --><!--END ifnot: topic.Grid -->
</div>
The viewmodel of course contains Topics(), and it contains a function that I want to have executed after the last element of Topics has been processed. How can I achieve this?

Knockout JS - tabs in usercontrol not formatting

I've drilled down so I'm pretty sure I know the right question to ask - let's see how I've done.
I have a single-page app using Knockout 3.4.0. A main page, with a number of attached user controls. I have a tab set defined on the main page, and it works fine:
<div class="newportal documentsView" id="documentsView">
<div class="tabs">
<ul data-bind="foreach: tabs">
<li><a data-bind="attr: { href: '#tab-' + name }, css: { selected: $root.currentTab() == $data }, click: $root.updateTab, text: name"></a></li>
</ul>
<!-- ko foreach: tabs -->
<div class="area" data-bind="attr: { id: 'tab-' + name }, template: { name: template, data: $data.model().viewModel }">
</div>
<!-- /ko -->
</div>
</div>
I have a second set of tabs defined on one of the controls that houses the contents for one of those top tabs. The code's all but identical to the top one, save for referring to different data:
EDIT - duplicate IDs and tag names altered at suggestion of commenter - no change to format or functionality.
<div class="newportal documentsView" id="bulkDocumentsView">
Welcome to Bulk Documents
<div class="tabs">
<ul data-bind="foreach: bulktabs">
<li><a data-bind="attr: { href: '#bulktab-' + name }, css: { selected: $root.currentBulkTab() == $data }, click: $root.updateBulkTab, text: name"></a>
<!-- ko if: $root.currentBulkTab() == $data -->
(*)
<!-- /ko -->
</li>
</ul>
<!-- ko foreach: bulktabs -->
<!-- ko if: $root.currentBulkTab() == $data -->
<div class="area" data-bind="attr: { id: 'bulktab-' + name }, template: { name: template, data: $data.model().viewModel }">
</div>
<!-- /ko -->
<!-- /ko -->
</div>
</div>
</div>
The "$root.currentBulkTab" ko conditionals are in there so I can confirm that the links are correctly holding the correct selected tag and "highlighting" correctly - they do, and are.
However, the final page is only formatting the top set as tabs, the second set are displaying as an unformatted UI set:
The functionality is right - showing correct selected page, etc. If i don't have the second ko conditional around the template section, it displays all three, another thing that I believe should clear once the tab formatting applies properly.
The css file being used is jquery-ui-1.10.3.custom.css - I can comment the file out and the top tabset mimics the behavior of the second one.
There's clearly some additional link or tag I need to hit to get the data on the sub-page to format properly, but I don't know what it is. Can you only have one tabset per page, and it needs to be some sort of "sub tabset" of which I'm unaware?
I'm assuming the styles should cascade through (The word "cascade" is part of their title) but do they somehow function differently?
Thoughts?
Yes, jQuery UI allows nested tabs. My guess is you are calling the jquery UI .tabs() command on a selected limited to the outer/top tab set only.
Here, I'm calling it on the class selector:
$(".tabs").tabs();
I've removed all external CSS and hard-coded the HTML since your <ul>s and <li>s are showing, since I don't think this is a Knockout issue. You can see the nested tabs and that they are clickable and hide non-selected content.
$(".tabs").tabs();
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" rel="stylesheet"/>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div>
<div class="tabs">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<div id="tab-1">
<div>
1 - Top level tab content
<div class="tabs">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<div id="bulktab-1">
1 - Second level tab content
</div>
<div id="bulktab-2">
2 - Second level tab content
</div>
<div id="bulktab-3">
3 - Second level tab content
</div>
</div>
</div>
</div>
<div id="tab-2">
<div>
2 - Top level tab content
</div>
</div>
<div id="tab-3">
<div>
3 - Top level tab content
</div>
</div>
</div>
</div>
I didn't scroll out enough, and if I had, you'd all have probably seen the problem.
The second snippet above is (was) contained within a conditional that checks that the dropdown has a selected value.
<!-- ko if: selectedSupplier() -->
<div class="newportal relative">
<div class="newportal documentsView" id="bulkDocumentsView">
Welcome to Bulk Documents
<div class="tabs">
<ul data-bind="foreach: bulktabs">
<li><a data-bind="attr: { href: '#bulktab-' + name }, css: { selected: $root.currentBulkTab() == $data }, click: $root.updateBulkTab, text: name"></a>
<!-- ko if: $root.currentBulkTab() == $data -->
(*)
<!-- /ko -->
</li>
</ul>
<!-- ko foreach: bulktabs -->
<!-- ko if: $root.currentBulkTab() == $data -->
<div class="area" data-bind="attr: { id: 'bulktab-' + name }, template: { name: template, data: $data.model().viewModel }">
</div>
<!-- /ko -->
<!-- /ko -->
</div>
</div>
</div>
<!-- /ko -->
It would appear to me that being within such a conditional blocks the stylesheet from properly seeing the code - I'm probably not saying that properly.
I move the conditional to only block the template underneath (and removing my test conditionals):
<div class="newportal relative">
<div class="newportal documentsView" id="bulkDocumentsView">
Welcome to Bulk Documents
<div class="tabs">
<ul data-bind="foreach: bulktabs">
<li><a data-bind="attr: { href: '#bulktab-' + name }, css: { selected: $root.currentBulkTab() == $data }, click: $root.updateBulkTab, text: name"></a>
</li>
</ul>
<!-- ko if: selectedSupplier() -->
<!-- ko foreach: bulktabs -->
<div class="area" data-bind="attr: { id: 'bulktab-' + name }, template: { name: template, data: $data.model().viewModel }">
</div>
<!-- /ko -->
<!-- /ko -->
</div>
</div>
</div>
And bingo.
Now that means it's going to be smarter for me to put a separate dropdown on each sub-page, but that'll solve a problem I was having on how to pass the selected values down to it anyway, so, you know, two birds.
98% finding the problem, and 2% fixing it.

How to Negate Array.protype.filter Function

I am using the Array.prototype.filter() method to filter an array into 2 sets of elements and then iterating through those 2 sets with Knockout, though I believe the issue is JS related. I would like to take the truthy elements and make set A and the falsy and make set B. So, for falsy I attempt to negate the function, but the following error occurs. I could create a second method to perform the negation, however I'm interested in this solution. Thanks.
Array.prototype.filter: argument is not a Function object
HTML:
<!-- ko foreach: Items.filter(IsSetA) -->
<h1>Is Set A</h1>
<!-- /ko -->
<!-- ko foreach: Items.filter(!IsSetA) -->
<h1>Is Set B</h1>
<!-- /ko -->
JS:
function IsSetA(item) {
return item.category === 'A';
}
Array.prototype.filter() requires you to pass a function that it will execute on each item. IsSetA by itself works because it's a function, but negating that with the ! operator casts it to a boolean value, which simply isn't something accepted by filter.
Alternatively, you could either create helper functions and use them in your template:
function IsSetATruthy(item) {
return IsSetA(item);
}
function IsSetAFalsy(item) {
return !IsSetA(item);
}
or write the same as inline functions:
<!-- ko foreach: Items.filter(function(item) { return IsSetA(item); }) -->
<h1>Is Set A</h1>
<!-- /ko -->
<!-- ko foreach: Items.filter(function(item) { return !IsSetA(item); }) -->
<h1>Is Set B</h1>
<!-- /ko -->
or use arrow functions if your target supports them:
<!-- ko foreach: Items.filter(item => IsSetA(item)) -->
<h1>Is Set A</h1>
<!-- /ko -->
<!-- ko foreach: Items.filter(item => !IsSetA(item)) -->
<h1>Is Set B</h1>
<!-- /ko -->
You're trying to negate a function reference. What you were trying to do is negate the result of the function. The positive case works (filter expects a function reference), but the negative will not, so negate the result manually:
<!-- ko foreach: Items.filter(IsSetA) -->
<h1>Is Set A</h1>
<!-- /ko -->
<!-- ko foreach: Items.filter(function(val){ return !IsSetA(val); }) -->
<h1>Is Set B</h1>
<!-- /ko -->

Knockout observable used in text binding not updating ui

This has been driving me nuts for hours now. I think I have tried everything on the javascript side. In the javascript side I have this value: resultItem.standingNo which is a knockout observable. This one works fine, it updates like it should. I've used subscribers and everything I can think of to see that the value is updated and also tried the same to update other observables to use instead of this one.
What happens. The initial value is rendered in the html when I create the observable. But the html bit never receives the updates. I have tried with and without .extend({ notify: 'always' }), I have tried observable, computed and pureComputed, none updates the ui. But all gets the new value. I have other observables in the same class which update fine. But they are not located in the same place in the html.
resultItem.shortName just below is not an observable and doesn't need to be updated.
So my question is, is there some limitation with the html that prevents some observables from updating? Because I can't see any problem with my code between the two "...". Where those are there is code that get observable updates with no problems. Any ideas?
<!-- ko foreach: { data: resultLists, as: 'resultList' } -->
<!-- ko if: resultList.visible -->
...
<!-- ko foreach: { data: resultList.resultItems, as: 'resultItem', afterRender: $parent.renderedHandler } -->
<!-- Competitor Info Row Start-->
<tr class="rowDetail" data-bind="toggleCompetitorDetails: resultItem.competitorSelected">
<td class="selected-competitor-content" colspan="100%">
<div class="selected-competitor-container" >
<div class="selected-competitor-image">
<div class="img" data-bind="attr: { style: 'background-image: url(' + resultList.getRandomImage() + ');' }"></div>
<div class="competitor-dot competitor-star" data-bind="attr: {class: resultItem.isTopCompetitor()}, style: { backgroundColor: resultItem.color.toString(), color: resultItem.color.toString() }"></div>
</div>
DOES NOTE UPDATE --> <div class="selected-competitor-position" data-bind="text: resultItem.standingNo"></div> <-- DOES NOTE UPDATE
<div class="selected-competitor-info">
<p class="selected-competitor-name" data-bind="text: resultItem.shortName"></p>
<p class="selected-competitor-captain" data-bind="text: resultItem.name"></p>
</div>
</div>
</td>
</tr>
<!-- Competitor Info Row End -->
...
<!-- /ko -->
<!-- /ko --><!-- ResultList visible end -->
<!-- /ko --><!-- ResultLists foreach End -->

Knockout: start observableArray with null

Seems that in knockout 3.2.0 they changed the behavior of ObservableArrays. In knockout 2, if I did:
var array = ko.observableArray(null)
console.log(array())
It would return me null. The same thing on knockout 3.2.0 doesn't happen because the observable array instead of null is create as an empty array.
This is my case:
<div>
<div class="spinner" data-bind="visible: comments() == null">
<!-- ko foreach: comments -->
...
<!-- /ko -->
</div>
I'd like to start showing in the comments div a spinner, and when the comments are populated, i'll hide the spinner and show the comments. I can't do data-bind="visible: comments().length == 0" because if the post has no comments, the comments array will have 0 length and the spinner will be shown forever.
How can I make this work?
This would work.
self.comments = ko.observableArray([]);
<!-- ko if: comments().length > 0 -->
Greater than zero // Show spinner
<!-- /ko -->
<!-- ko ifnot: comments().length > 0 -->
No greater than zero // No spinner needed
<!-- /ko -->
Let me know what you think.

Categories

Resources