Knockout Conditional Tool - javascript

I need to add a conditional tool tip based on the menu item name. I am new to knockout and not sure on the best way to approach this.
<div id="pageMenu" data-bind="foreach: Pages">
<div data-bind="visible: $data.accessAllowed() ">
<a data-bind="click: $parent.openPage, css: { 'selected': Selected }"><div data-bind="text: MenuItemName"></div></a>

In this example title depends whether foo and bar have the same text. If you change foo's text to be foo for example the title will be title2
function bla(){
self.text = ko.observable("Some text");
self.bar = ko.observable("bar");
self.foo = ko.observable("bar");
}
ko.applyBindings(new bla());
<p data-bind="text: text,
attr:{
'title': bar() === foo() ? 'title1' : 'title2'
}">
</p>

you can define function in your view model where you can put your condition for showing the tooltip or hide it like this :
function ViewModel(menuItemName) {
......
self.visibleDiv = ko.computed(function () {
//here you have the item that your are displaying.
//So put here your logic and then
//you should return false or true
.......
});
}
then in your html
<div class="editor-label" data-bind="text: MenuItemName,visible: $root.visibleDiv(MenuItemName)">

This is should be what you are looking for.
<div id="pageMenu" data-bind="foreach: Pages">
<div data-bind="visible: $data.accessAllowed() ">
<a data-bind="click: $parent.openPage, css: { 'selected': Selected }">
<div data-bind="attr : {'title' : ($data.MenuItemName() == 'criteria'? 'tooltip1' : 'tooltip2')} "></div>
</a>
</div>
</div>
I hope it helps.

Related

Knockout binding with a handle to templated element

Knockout is just great but I'm a little bit confused on how to deal with DOM elements after they are generated. For example I have a collection of users. Each user has an Id:
var user = {
id : 123,
name : 'testUser',
age: 45
};
Using Knockout I bind my collection of described above data structure with the following html template:
<div data-bind="foreach: users">
<div class='user-wrapper'>
<span data-bind="text: name"></span>
<span data-bind="text: age"></span>
</div>
</div>
and now I want to change background color on user click:
$(".user-wrapper").click(function (e) {
//doesn't work - toggelClass is not a function
e.target.toggleClass("user-selected");
});
Once I hit a user target could be different (span or div), I need to make sure that I'm getting the right div. Moreover e.target doesn't work with "not a fucntion" error.
How can I access calling element to toggle the class?
How can I get a user id of that element to access other controls related to that id?
You should use the click binding in conjunction with the css binding:
<div data-bind="foreach: users">
<div class='user-wrapper' data-bind="click: toggleSelected, css: { 'user-selected': isSelected }">
<span data-bind="text: name"></span>
<span data-bind="text: age"></span>
</div>
</div>
Note that if you're ever tempted to use jQuery to manipulate DOM while you're using KnockoutJS (or client side MVVM libraries in general): don't. If you absolutely must, you probably need a custom binding handler, much like you'd use a directive for DOM manipulation in "that other" mvvm framework.
Here's a demo:
var user = {
id : 123,
name : 'testUser',
age: 45
};
var UserVm = function(data) {
var self = this;
self.name = data.name;
self.age = data.age;
self.isSelected = ko.observable(false);
self.toggleSelected = function() {
self.isSelected(!self.isSelected());
}
};
ko.applyBindings({ users: [new UserVm(user)] });
.user-selected { background-color: red; }
.user-wrapper:hover { cursor: pointer; background-color: pink; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach: users">
<div class='user-wrapper' data-bind="click: toggleSelected, css: { 'user-selected': isSelected }">
<span data-bind="text: name"></span>
<span data-bind="text: age"></span>
</div>
</div>

Assign Dynamic templates

I am trying to use KnockoutJS to develop a wizard for my asp.net app.
This is my first attempt at using KO.
What I am trying to achieve is assign dynamic templates based on a Click event of an anchor tag.
My HTML looks like this
<script id="ParamHomeTmpl" type="text/html">
<section class="alert alert-info">
<div class="panel-heading h3 blackincolor"><i class="fa fa-exclamation-circle redincolor" style="margin-right: 5px"></i>Please Select Parameter Type</div>
<ul class="blackincolor list-group">
<li><a class="list-group-item list-group-item-info" data-bind="click: templateToUse" href="#" id="InputType"><b>Input Type:</b> Gives an Option to Select your Key-Value Pairs.</a></li>
<li><a class="list-group-item list-group-item-success" data-bind="click: templateToUse" href="#" id="ListType"><b>List Type:</b> You can type in a Key and insert a list of values and select one of the values that you created.</a></li>
</ul>
</section>
</script>
<script id="InputTypeTmpl" type="text/html">
<div>
<p>Input Type</p>
</div>
</script>
<script id="ListTypeTmpl" type="text/html">
<div>
<p>ListType</p>
</div>
</script>
<script id="BlankTmpl" type="text/html">
<div>
<p>Blank</p>
</div>
</script>
<div class="tab-pane" id="SelectParamType" data-bind="template: { name: templateToUse }">
</div>
And finally the actual JS is:
var viewModel = {
currTemplate: function () {
return "paramHome";
},
paramType: ko.observable("Metadata")
};
viewModel.secondStep = function (data, event) {
// return (event.target.id);
console.log(data);
};
viewModel.templateToUse = function (data, event) {
try {
alert(event.target.id);
switch (event.target.id) {
case "InputType":
return "InputTypeTmpl";
case "ListType":
return "ListTypeTmpl";
default:
return "BlankTmpl";
}
}
catch (err) { return "ParamHomeTmpl" ;}
};
ko.applyBindings(viewModel);
The issue is that when I click the anchor tag from the first step "Select Param Type", the template is not swapped automatically based on the click event target.
I am not sure what am I doing wrong here.
Jsfiddle: http://jsfiddle.net/sourabhtewari/c8tm1193/
I think your approach -- using templates -- is wrong. Dynamic HTML is what the if binding is for. The only legitimate use for templates (imo) is recursive HTML structure.
Update I no longer subscribe to this view. Templates are useful for recursive HTML and when a section of code may take several forms. Bind to a variable and update the variable with the proper template name. Original answer continues below.
I've made the click bindings set an observable that controls which section is displayed.
var viewModel = {
section: ko.observable('blank'),
paramType: ko.observable("Metadata")
};
viewModel.secondStep = function(data, event) {
// return (event.target.id);
console.log(data);
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<section class="alert alert-info">
<div class="panel-heading h3 blackincolor"><i class="fa fa-exclamation-circle redincolor" style="margin-right: 5px"></i>Please Select Parameter Type</div>
<ul class="blackincolor list-group">
<li><a class="list-group-item list-group-item-info" data-bind="click: section.bind(0, 'input')" href="#" id="InputType"><b>Input Type:</b> Gives an Option to Select your Key-Value Pairs.</a>
</li>
<li><a class="list-group-item list-group-item-success" data-bind="click: section.bind(0, 'list')" href="#" id="ListType"><b>List Type:</b> You can type in a Key and insert a list of values and select one of the values that you created.</a>
</li>
</ul>
</section>
<div data-bind="if:section()=='input'">
<p>Input Type</p>
</div>
<div data-bind="if:section()=='list'">
<p>ListType</p>
</div>
<div data-bind="if:section()=='blank'">
<p>Blank</p>
</div>
I know this is an old question, but I came across my previous answer and felt it needed to be fixed.
The main problem with your code is that you're using the function you've bound to the click as the template name. You should have a separate observable for the template name, and the function should set its value:
<div class="tab-pane" id="SelectParamType" data-bind="template: currTemplate">
...
var viewModel = {
currTemplate: ko.observable('ParamHomeTmpl'),
paramType: ko.observable("Metadata")
};
Also, you have no breaks in your switch, so it always falls through to default, though you fixed this in your answer.
A secondary problem is that clicking on the bold text in the link causes event.target to be the <b> element rather than the <a>, so you have no id. You can avoid this problem by binding the template name to the function call:
data-bind="click: templateToUse.bind($root, 'InputTypeTmpl')"
and using the data parameter in the function, which then becomes very simple:
viewModel.templateToUse = function(data) {
viewModel.currTemplate(data);
};
In fact, you can do away with the function entirely and bind directly to the template name observable:
data-bind="click: currTemplate.bind($root, 'InputTypeTmpl')"
var viewModel = {
currTemplate: ko.observable('ParamHomeTmpl'),
paramType: ko.observable("Metadata")
};
viewModel.secondStep = function(data, event) {
// return (event.target.id);
console.log(data);
};
viewModel.templateToUse = function(data) {
viewModel.currTemplate(data);
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script id="ParamHomeTmpl" type="text/html">
<section class="alert alert-info">
<div class="panel-heading h3 blackincolor"><i class="fa fa-exclamation-circle redincolor" style="margin-right: 5px"></i>Please Select Parameter Type</div>
<ul class="blackincolor list-group">
<li><a class="list-group-item list-group-item-info" data-bind="click: currTemplate.bind($root, 'InputTypeTmpl')" href="#" id="InputType"><b>Input Type:</b> Gives an Option to Select your Key-Value Pairs.</a>
</li>
<li><a class="list-group-item list-group-item-success" data-bind="click: currTemplate.bind($root, 'ListTypeTmpl')" href="#" id="ListType"><b>List Type:</b> You can type in a Key and insert a list of values and select one of the values that you created.</a>
</li>
</ul>
</section>
</script>
<script id="InputTypeTmpl" type="text/html">
<div>
<p>Input Type</p>
</div>
</script>
<script id="ListTypeTmpl" type="text/html">
<div>
<p>ListType</p>
</div>
</script>
<script id="BlankTmpl" type="text/html">
<div>
<p>Blank</p>
</div>
</script>
<div class="tab-pane" id="SelectParamType" data-bind="template: currTemplate">
</div>
The problem is fixed using
applyBindingsToNode
Code:
viewModel.templateToUse = function(data, event) {
try {
switch (event.target.id) {
case "InputType":
templateType = "InputTypeTmpl";
break;
case "ListType":
templateType = "ListTypeTmpl";
break;
case "FileType":
templateType = "FileTypeTmpl";
break;
case "DataBaseType":
templateType = "DataBaseTypeTmpl";
break;
default:
return "BlankTmpl";
}
} catch (err) {
return "BlankTmpl";
}
ko.applyBindingsToNode(document.getElementById("Attributes"), {
template: {
name: templateType
}
});
Fiddle: http://jsfiddle.net/sourabhtewari/c8tm1193/1

How to ng-style one element it's $index created by ng-repeat?

I have 2 directives: wa-hotspots & wa-tooltips.
On ng-mouseover of wa-hotspots it takes that $index of wa-hotspot and sets the visibility and position of wa-tooltip via ng-class:on and ng-style="tooltipCoords" by matching indexes.
Note: Since wa-hotspots & wa-tooltips share the same collection page.hotspots and therefore they share teh same index via ng-repeat
Problem:
When you hover over wa-hotspots it sets the ng-style position to ALL of the elements in wa-tooltips. I only want it ot set the proper matching index. Since the visiblity is controlled by ng-class, This doesn't really matter but it seems like it's extra overhead that could be avoid.
Therefore:
Question:
How can I make sure that my ng-style isn't styling all the wa-tooltips on hover of wa-hotspots? But rather, style only the tooltip that matches the proper shared index?
<ul id="wa-page-{{page.pageindex}}" class="wa-page-inner" ng-mouseleave="pageLeave()">
<li wa-hotspots
<map name="wa-page-hotspot-{{page.pageindex}}">
<area ng-repeat="hotspot in page.hotspots"
class="page-hotspot"
shape="{{hotspot.areashape}}"
coords="{{hotspot.coordinatetag_scaled}}"
ng-mouseover="showTooltip($index, hotspot.coordinatetag_scaled)"
ng-mouseout="hideTooltip()">
</map>
</li>
<li class="tooltip-wrapper">
<ul class="tooltip">
<li wa-tooltips
ng-repeat="hotspot in page.hotspots"
ng-class="{on: hovered.index == $index}"
ng-mouseover="hovered.active == true"
ng-mouseout="hovered.active == false"
ng-style="tooltipCoords" hotspot="hotspot">
</li>
</ul>
</li>
</ul>
tooltip:
You need to make it per item like in your case - hotspot.tooltipCoords then set that variable by index.
you can do the check inside the expression function.
Heres a fiddle
<div ng-controller="MyCtrl">
<div ng-repeat="item in items" ng-style="isChecked($index)">
name: {{item.name}}, {{item.title}}
<input type="checkbox" ng-model="item.checked" />
</div>
</div>
...
$scope.isChecked = function($index){
var color = $scope.items[$index].checked ? 'red' : 'blue';
return {color:color};
}
Instead of
ng-mouseover="hovered.active == true"
ng-mouseout="hovered.active == false"
use
ng-mouseover="hotspot.class== 'active'"
ng-mouseout="hotspot.class== ''"
and after that you can use hotspot.class in ng-class ie:
ng-class="hotspot.class"
Please see demo below:
var app = angular.module('app', []);
app.controller('homeCtrl', function($scope) {
$scope.items = [{
id: 1
}, {
id: 2
}, {
id: 3
}, {
id: 4
}]
});
.red {
background-color: yellow;
}
p {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="homeCtrl">
<p ng-repeat="i in items" ng-mouseover="i.class='red'" ng-class="i.class" ng-mouseout="i.class = ''">{{i.id}}</p>
</div>
</div>
Use the below one
<div class="col-md-4 col-xs-12 col-lg-4" ng-repeat="obj in items">
<button type="button" class="btn btn-sm pull-right" ng-class="obj.class" ng-click="obj.class='test'" >
Write a new class "test". Instead of click you can use the same in ngmouseover

Keep track of tabs with knockoutjs + twitter bootstrap

I'm trying to keep track of the selected tab in the view model but I can't seem to make it work.
In the following code when you click a tab the header will update correctly but the content of the tab is not displayed. If you remove , click: $parent.selectSection then the contents are shown but the header does not update.
Now if you remove the data-bind="css: { active: selected }" from the li then it seems to work when you click the tabs but the button to select the second tab doesn't.
How can I make this work?
See: http://jsfiddle.net/5PgE2/3/
HTML:
<h3>
<span>Selected: </span>
<span data-bind="text: selectedSection().name" />
</h3>
<div class="tabbable">
<ul class="nav nav-tabs" data-bind="foreach: sections">
<li data-bind="css: { active: selected }">
<a data-bind="attr: { href: '#tab' + name }
, click: $parent.selectSection" data-toggle="tab">
<span data-bind="text: name" />
</a>
</li>
</ul>
<div class="tab-content" data-bind="foreach: sections">
<div class="tab-pane" data-bind="attr: { id: 'tab' + name }">
<span data-bind="text: 'In section: ' + name" />
</div>
</div>
</div>
<button data-bind="click: selectTwo">Select tab Two</button>
JS:
var Section = function (name) {
this.name = name;
this.selected = ko.observable(false);
}
var ViewModel = function () {
var self = this;
self.sections = ko.observableArray([new Section('One'),
new Section('Two'),
new Section('Three')]);
self.selectedSection = ko.observable(new Section(''));
self.selectSection = function (s) {
self.selectedSection().selected(false);
self.selectedSection(s);
self.selectedSection().selected(true);
}
self.selectTwo = function() { self.selectSection(self.sections()[1]); }
}
ko.applyBindings(new ViewModel());
There are several ways that you can handle this either using bootstrap's JS or by just having Knockout add/remove the active class.
To do this just with Knockout, here is one solution where the Section itself has a computed to determine if it is currently selected.
var Section = function (name, selected) {
this.name = name;
this.isSelected = ko.computed(function() {
return this === selected();
}, this);
}
var ViewModel = function () {
var self = this;
self.selectedSection = ko.observable();
self.sections = ko.observableArray([
new Section('One', self.selectedSection),
new Section('Two', self.selectedSection),
new Section('Three', self.selectedSection)
]);
//inialize to the first section
self.selectedSection(self.sections()[0]);
}
ko.applyBindings(new ViewModel());
Markup would look like:
<div class="tabbable">
<ul class="nav nav-tabs" data-bind="foreach: sections">
<li data-bind="css: { active: isSelected }">
<a href="#" data-bind="click: $parent.selectedSection">
<span data-bind="text: name" />
</a>
</li>
</ul>
<div class="tab-content" data-bind="foreach: sections">
<div class="tab-pane" data-bind="css: { active: isSelected }">
<span data-bind="text: 'In section: ' + name" />
</div>
</div>
</div>
Sample here: http://jsfiddle.net/rniemeyer/cGMTV/
There are a number of variations that you could use, but I think that this is a simple approach.
Here is a tweak where the active tab used the section name as a template: http://jsfiddle.net/rniemeyer/wbtvM/

Binding to class in knockout.js

I want to bind class of LI/DIV. I'm using knockout.js. I don't know how to make it works. This is my code:
<div id="users-list2" class="span8">
<div class="tabbable">
<!-- Only required for left/right tabs -->
<ul class="nav nav-tabs" data-bind="foreach: conversations">
<li data-bind="click: function () { $root.tabClick(username); }, attr:{ 'class': cls}" style="float:left">
<a class="user-box-name"
data-bind="text: username, attr:{ 'href':'#'+ username }, event: { contextmenu: $root.closeTab }"></a>
</li>
</ul>
<div class="tab-content" data-bind="foreach: conversations">
<div data-bind="attr:{ 'id': username, 'class': 'tab-pane ' + cls}">
<div id="chat-list" class="span12" data-bind="foreach: messages">
<ul>
<li>
<div class="chat-listitem-username" data-bind="text: username">
</div>
<div class="chat-listitem-message" data-bind="html: content">
</div>
<div class="chat-listitem-timestamp" data-bind="text: timestamp.toLocaleTimeString()">
</div>
</li>
</ul>
</div>
</div>
and viewModel:
chatR.conversation = function (username) {
var self = this;
self.messages = ko.observableArray();
self.username = username;
self.test = function (x) { alert(x.username) };
self.cls = "";
}
I want to change cls to "active" when specific tab is clicked and change all others cls to "". It's not working. What am I doing wrong? Classes like "tabbable" and "nav" are defined by bootstrap.js.
EDIT:
This is how I want to chnage cls:
self.tabClick = function (username) {
self.currentConversation = username;
for (i = 0; i < self.conversations().length; i++) {
if (self.conversations()[i].username == username) {
self.conversations()[i].cls = "active";
}
else {
self.conversations()[i].cls = "";
}
}
}
EDIT2:
Changes from comment work, but I have another problem. Li has class "active" but DIC gets:
<div id="aaa_1" class="tab-pane function d(){if(0<arguments.length)
{if(!d.equalityComparer||!d.equalityComparer(c,arguments[0]))d.H(),c=arguments[0],d.G();return this}b.r.Va(d);return c}"
data-bind="attr:{ 'id': username, 'class':'tab-pane '+cls}">
What is wrong here?
Posting the details as an answer.
First Step was to make the cls property an observable and change the code to and change your code accordingly self.conversations()[i].cls("active"); or self.conversations()[i].cls("");
For the second as in edit.
You need output the value of cls and not cls as a whole
So change this
<div data-bind="attr:{ 'id': username, 'class': 'tab-pane ' + cls}">
to
<div data-bind="attr:{ 'id': username, 'class': 'tab-pane ' + cls()}">
Answer to edit 2: instead of
'tab-pane ' + cls
use
'tab-pane ' + cls()
Knockout has special binding for classes, a css binding (http://knockoutjs.com/documentation/css-binding.html).
Example:
<div data-bind='css: {classname: bla() == "something"}'>...</div>

Categories

Resources