I am generating the following in a for loop (ignore the jade template syntax for now):
ul.nav.nav-tabs(data-bind="foreach: channels", id="galery_tabs")
li
a(data-toggle='tab', data-bind="attr: {href: '#tab_section_' + __kb.object.cid},html: name() + ' <i class=\"icon-remove close\" data-binding=\"click: removeChannel\"></i>'")
When I call .applyBindings it will correctly render my A element with the Icon that has a binding inside.
Question: How do I make second pass apply to ensure the dynamically generated binding is now applied to all Icon elements as well?
You don't need to use the html binding for the scenario.
You can just put your <i> inside the <a> and you can use the KO container-less syntax to add the name property before the icon:
ul.nav.nav-tabs(data-bind="foreach: channels", id="galery_tabs")
li
a(data-toggle='tab', data-bind="attr: {href: '#tab_section_' + __kb.object.cid}")
// ko text: name
// /ko
i.icon-remove.close(data-binding="click: removeChannel")
And the generated HTML will look like this:
<ul data-bind="foreach: channels" id="galery_tabs" class="nav nav-tabs">
<li><a data-toggle="tab" data-bind="attr: {href: '#tab_section_' + __kb.object.cid}">
<!-- ko text: name -->
<!-- /ko--><i data-binding="click: removeChannel" class="icon-remove close"></i></a></li>
</ul>
Related
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.
Regular icon tag is normally used this way:
<i class="fa fa-cultery"></i>
but for some reason I tried to store the icon class names inside a viewModel like:
self.interviewScheduleIconOptions = [
"icon-chair",
"icon-clock",
"icon-dollar",
"icon-monitor",
"icon-star",
"icon-subway",
];
and tried to do something like
<i data-bind="attr: {class: $data}"></i>
before this, I tried to test
to see if it works with data-bind.
The icon did not appear and when I inspected the HTML, it gave me:
<i data-bind="attr: {class: 'fa fa-cultery'}" class="fa fa-cultery"></i>
so it gave the right class, but the icon did not appear.
Any idea?
Please try <i data-bind="attr: {'class': $data}"></i> which means single quote around class
Reason
Because, class is not a legal javascript variable name
Note: Applying attributes whose names aren’t legal JavaScript variable names
If you want to apply the attribute data-something, you can’t write this:
<div data-bind="attr: { data-something: someValue }">...</div>
… because data-something isn’t a legal identifier name at that point. The solution is simple: just wrap the identifier name in quotes so that it becomes a string literal, which is legal in a JavaScript object literal. For example,
<div data-bind="attr: { 'data-something': someValue }">...</div>
You seem to be referring to two different versions of font-awesome. Classes like fa fa-search belong to FA 4.7.2. While classes like icon-search belong to version 3.2.1
You can use the css binding which is meant to be used for CSS classes.
<i data-bind="css: $root.class"></i>
Font-Awesome 4.7.2
var Model = function() {
var self= this;
self.class= ko.observable( "fa fa-search");
}
ko.applyBindings(new Model());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<i data-bind="css: $root.class"></i>
Font-Awesome 3.2.1
var Model = function() {
var self= this;
self.class= ko.observable( "icon-search");
self.interviewScheduleIconOptions = ko.observableArray([
"icon-chair",
"icon-clock",
"icon-dollar",
"icon-monitor",
"icon-star",
"icon-subway",
]);
}
ko.applyBindings(new Model());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.1/css/font-awesome.min.css" rel="stylesheet"/>
<i data-bind="css: $root.class"></i>
<span data-bind="foreach: interviewScheduleIconOptions">
<br/>
<span data-bind="text: ($index() + 1)"></span>
<i data-bind="css: $data"></i>
</span>
I use the following code snippet to list an array of objects in html document
, it shows the list of link accurately ...
<div ng-repeat="item in List">
<a ng-click="Show('{{item.name}}');"> ShowName </a>
</div>
it outputs the following html code:
<a ng-click="Show('name1')">ShowName</a>
.
.
.
<a ng-click="Show('//upto nth name')">ShowName</a>
Here is my angular code :
$scope.Show=
function(getName)
{
alert(getName);
}
but when I click on any link it shows "{{item.name}}" instead of showing "name1" or respective name in the alertbox...
I also tried
Show('{{item.name}}');
but the result is same...
what I am doing wrong????
You are not supposed to use {{}} inside an Angular directive. Your code should be like this:
<div ng-repeat="item in List">
<a ng-click="Show(item.name)"> ShowName </a>
</div>
You can either use following style
<li ng-repeat="item in List">
{{item.name}}
</li>
here $index represent index of your array. i prefer to use this because here you can access full object.then just do what you want.
$scope.clickit = function(x) {
console.log($scope.List[x])
}
if you don't like the above style you can use the below style
<li ng-repeat="item in List">
{{item.name}}
</li>
The problem here is that you are using ' and {} when passing the variable as an argument to the function.
Change
<a ng-click="Show('{{item.name}}');"> ShowName </a>
to
<a ng-click="Show(item.name);"> ShowName </a>
Here's a snippet from a knockout based answer editor
<!-- ko foreach: Answers -->
<div class="qa-box" data-bindx="event: { mousedown: mouseDown, dragend: dragEnd, dragstart: dragStart }">
<div class="qa-body">
<div class="radio">
<label>
<input type="radio" data-bind="attr: { name: 'Q' + $parentContext.$index(), value: $index }, checked: $parent.CorrectAnswer" /><span></span>
Tick the correct answer
<span data-bind="text:$parent.CorrectAnswer"></span>
</label>
<a href="#" data-bind="click: $parent.remove.bind($parent)">
<i class="fa fa-times"></i>
Remove this answer
</a>
<div class="form-control" contenteditable="true" data-bind="ckeditor: $data, attr: { id: 'Q' + $parentContext.$index() + 'A' + $index() }"></div>
</div>
</div>
</div>
<!-- /ko -->
<div>CorrectAnswer: <span data-bind="text: CorrectAnswer"></span></div>
You'll notice I put a bound span on the end of the radio button label so I can see what happens to the CorrectAnswer observable when I interact with the UI. This is how I know it's correctly bound to the view model. Clicking a radio button or its label changes the value of CorrectAnswer exactly as intended.
This also allows me to know that CorrectAnswer contains the value I expected.
Let's take a closer look at the binding in case it isn't obvious.
attr: { name: 'Q'+$parentContext.$index(), value: $index }, checked: $parent.CorrectAnswer
All the answers for a given question get the same name Qx and a value provided by the item's list position. When an item is clicked its list position is written to CorrectAnswer. This does happen, as evidence by the new value showing up in the telltale div.
So, what could be preventing the UI from initialising as checked when everything else is fine?
It isn't an initialisation problem, it's a type compatibility problem. The value of a radio input is of type string. The value provided by my view model is of type number. Knockout does a strong comparison and does not recognise a match.
See also Radio buttons Knockoutjs
I have the following knockout bindings...
<ul class="list-group" data-bind="foreach: $parent.levels">
<li class="list-group-item">
<img alt="level" class="pic" data-bind="attr:{src: '../../Content/images/levels/' + $index() + '.png'}" />
<button class="btn btn-default piclabels" data-bind="click:$parent.startingLevel.bind($parent,$index()),text:$data,css:{active:$parent.startingLevel() == $index()}"></button>
</li>
</ul>
I want to remove the img tag and instead apply background images to the button. I have tried multiple examples all over the web, including from knockout, but I haven't been able to get it to work.
data-bind="style: {background: 'url('../../Content/images/levels/' + $index() + '.png')' repeat-none left}"
What am I doing wrong?
When using the style binding, each style needs to return a string for the whole value. So if you are using single quotes such as in a url you need to escape them using backslash \. Instead of repeat-none, I think you meant to use no-repeat.
data-bind="style: {background: 'url(\'../../Content/images/levels/' + $index() + '.png\') no-repeat left' }"
JsFiddle