Foreach: Toggle each icon - KnockoutJS - javascript

I am trying to toggle the plus symbol to minus sign, once after immediately that section get clicked and expanded. Following the code I have tried.
<div>
<ul style="list-style: none">
<li data-bind="foreach: model">
<div id="panelHeading">
<i class="fa fa-plus" style="padding-right: 5px;">+</i>
<span data-bind="text: Main"></span>
</div>
<div id="panelContent" data-bind="if: show">
<ul id="clustersList" data-bind="foreach: Sub" style="list-style: none">
<li><span style="padding-left: 20px;" data-bind="text: $data"></span></li>
</ul>
</div>
</li>
</ul>
</div>
=== JS ====
var viewModel = function() {
var self = this;
self.model = ko.observableArray([{
Main: "Main1",
Sub: ["hello", "hi"],
show: ko.observable(false)
}, {
Main: "Main2",
Sub: ["one", "two"],
show: ko.observable(false)
}]);
self.toggleShow = function (item) {
$('this a').find('i').toggleClass('fa fa-plus fa fa-minus');
var index = self.model.indexOf(item);
if (item.show())
self.model()[index].show(false);
else
self.model()[index].show(true);
}
}
ko.applyBindings(new viewModel());
Please check my Fiddle here.
Any suggestion would be helpful.

Just change your HTML to apply the correct style based on the current value of show:
<i class="fa" data-bind="css: { 'fa-plus': !show(), 'fa-minus': show() }"></i>
And in your JS:
self.toggleShow = function (item) {
item.show(!item.show());
};
See Fiddle

Related

How to display loader symbol ONLY beside a specific item which was clicked in ng-repeat

The problem here is, the loader symbol is getting displayed beside every item in the ng-repeat array. How can I display the loader symbol just beside the item which was clicked
I tried using $index but no luck.
I created a working example using CodePen. Could anyone please guide how to do it.
Below are excerpts from my app.
HTML Code
<ul>
<li class="listItems" ng-repeat="user in userDetails">
{{user.name}}, {{user.job}}
<span class="glyphicon glyphicon-ok markAsChecked" ng-click="markAsChecked()"></span>
<span ng-show="isLoaderActivated" class="glyphicon glyphicon-repeat loader"></span>
</li>
</ul>
JS Code
$scope.markAsChecked = function() {
console.log("clicked");
$scope.isLoaderActivated = true;
$timeout(function() {
return ($scope.isLoaderActivated = false);
}, 2000);
};
You can find solution CodePan
Html Code
<ul>
<li class="listItems" ng-repeat="user in userDetails">
{{user.name}}, {{user.job}}
<span class="glyphicon glyphicon-ok markAsChecked" ng-click="markAsChecked(user)"></span>
<span ng-show="user.isLoaderActivated" class="glyphicon glyphicon-repeat loader"></span>
JS Code
$scope.markAsChecked = function(item) {
console.log("clicked");
item.isLoaderActivated = true;
$timeout(function() {
item.isLoaderActivated = false;
}, 2000);
};
You can pass object in your example is user to the function and do the actions on it instead $scope which is global
Html:
<li class="listItems" ng-repeat="user in userDetails">
{{user.name}}, {{user.job}}
<span class="glyphicon glyphicon-ok markAsChecked" ng-click="markAsChecked(user)"></span>
<span ng-show="user.isLoaderActivated" class="glyphicon glyphicon-repeat loader"></span>
</li>
Controller:
$scope.markAsChecked = function(item) {
console.log("clicked");
item.isLoaderActivated = true;
$timeout(function() {
item.isLoaderActivated = false;
}, 2000);
};

autocomplete search in ng-repeat in angularjs?

I my code i have 3 ng-repeats based on key press event value i am getting data from service and iam consuming
my html code is:
<div class="input-group" ng-controller="sidemenu">
<input class="form-control" placeholder="Enter location, builder or project" ng-model="NodeId_1" autofocus ng-keyup="getValue($event.keyCode)"/>
<div class="search-datalist" ng-if="showsearch">
<ul ng-if="resultOfBuldDet.length>0">
<span class="result-hd">Builders</span>
<li ng-repeat="bud in resultOfBuldDet" ng-class="{active :2}" ng-click="searchFilter(bud)"><i class="fa fa-map-marker"></i> {{bud.builders_name}}</li>
</ul>
<ul ng-if="resultOfPropDet.length>0">
<span class="result-hd">Properties</span>
<li ng-repeat="prop in resultOfPropDet" ng-click="searchFilter(prop)"><i class="fa fa-map-marker"></i> {{prop.property_name}} ,{{prop.hp_city.city_name}},{{prop.hp_location.location_name}} </li>
</ul>
<ul ng-if="resultOfCityDet.length>0">
<span class="result-hd">cities</span>
<li ng-repeat="city in resultOfCityDet" ng-click="searchFilter(city)"><i class="fa fa-map-marker"> </i> {{city.city_name}}</li>
</ul>
<ul ng-if="resultOfLocaDet.length>0">
<span class="result-hd">Location</span>
<li ng-repeat="loc in resultOfLocaDet" ng-click="searchFilter(loc)"><i class="fa fa-map-marker"></i> {{loc.location_name}},{{loc.hp_city.city_name}}</li>
</ul>
<ul ng-if="resultOfSubLocaDet.length>0">
<span class="result-hd">sub Location</span>
<li ng-repeat="subloc in resultOfSubLocaDet" ng-click=" searchFilter(subloc)"><i class="fa fa-map-marker"></i> {{subloc.sub_location_name}},{{subloc.hp_location.location_name}},{{subloc.hp_location.hp_city.city_name}}</li>
</ul>
</div>
</div>
my controller js code:
sidemenu.controller('sidemenu', ['$scope', '$rootScope', 'allServices'
function(a, b,e) {
a.getValue = function(key) {
if (key == 8 && a.NodeId_1.length <= 2) {
a.resultOfPropDet = "";
a.resultOfBuldDet = "";
a.resultOfLocaDet = "";
a.resultOfCityDet = "";
a.resultOfSubLocaDet = "";
}
if (a.NodeId_1.length > 2) {
e.searchList(a.NodeId_1).then(function(result) {
a.resultOfPropDet = result.data.resultOfPropDet;
a.resultOfBuldDet = result.data.resultOfBuldDet;
a.resultOfLocaDet = result.data.resultOfLocaDet;
a.resultOfCityDet = result.data.resultOfCityDet;
a.resultOfSubLocaDet = result.data.resultOfSubLocaDet;
a.showsearch = true;
}, function(error) {
});
}
}
});
so when i am moving up and down arrows in keyboard.how to highlight the particular rows and trigger the particular function
like this

How to filter data with computed using both knockout data and "external" data

Background: I'm in the process of moving javascript logic in a legacy system over to knockout to get more structure and having to write less code in the future. Due to time constraints I can only move parts of the code over to knockout between each deploy.
Problem: Some of the data is generated by legacy code and some of the data is generated by knockout and I'm having problem with creating knockout logic to handle the following scenario (see below for code snippet and a JSFiddle link to the same code). The product radio buttons are not generated by knockout but the offer lists are. The key buttons to filter the offer lists works like I want them to, but I havn't managed to figure out how to get only the offers for product 1 listed under Product 1 and only offers for product 2 listed under Product 2. Anyone that could help?
I assume that if the product headers were generated from the data in the knockout view model my problem wouldn't be so hard to solve, but as you can see this isn't the case.
https://jsfiddle.net/b6er4wke/3/
function myViewModel() {
var self = this;
self.wrappedProducts = ko.observableArray();
self.availableOffers = ko.observableArray();
self.filterKey = ko.observable(1);
filteredItems = ko.computed(function() {
return ko.utils.arrayFilter(self.availableOffers(), function (offer) {
var isCorrectKey = offer.key == self.filterKey();
return (isCorrectKey);
});
});
self.filter = function(keyFilter) {
self.filterKey(keyFilter);
};
(function() {
// Products
self.wrappedProducts.push({"prod":"1"});
self.wrappedProducts.push({"prod":"2"});
// Offers
self.availableOffers.push({"name": "offer1", "key": "1", "prod": 1});
self.availableOffers.push({"name": "offer2", "key": "2", "prod": 1});
self.availableOffers.push({"name": "offer3", "key": "2", "prod": 2});
})();
}
var viewModel = new myViewModel();
ko.applyBindings(viewModel);
ul, h4 {margin-top: 0px; margin-bottom:0px;}
label {font-weight:bold;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h4>All products</h4>
<ul data-bind="foreach: wrappedProducts">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
<h4>Available offers</h4>
<ul data-bind="foreach: availableOffers">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
<div>
<button data-bind="click: function() {filter(1);}">key1</button>
<button data-bind="click: function() {filter(2);}">key2</button>
filterKey = <span data-bind="text: filterKey"></span>
</div>
<hr>
<label for="prod1">Product 1</label>
<ul data-bind="foreach: filteredItems">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
<label for="prod2">Product 2</label>
<ul data-bind="foreach: filteredItems">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
Knockout automatically wraps your binding expressions in a computed, so you can actually simply make filteredItems a regular function and call it with the product you want to display as a parameter:
filteredItems = function filteredItems(product) {
return ko.utils.arrayFilter(self.availableOffers(), function (offer) {
return offer.key == self.filterKey() && offer.prod == product;
});
};
<label for="prod1">Product 1</label>
<ul data-bind="foreach: filteredItems(1)">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
<label for="prod2">Product 2</label>
<ul data-bind="foreach: filteredItems(2)">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
and still have it update automatically when availableOffers changes.
function myViewModel() {
var self = this;
self.wrappedProducts = ko.observableArray();
self.availableOffers = ko.observableArray();
self.filterKey = ko.observable(1);
filteredItems = function filteredItems(product) {
return ko.utils.arrayFilter(self.availableOffers(), function (offer) {
return offer.key == self.filterKey() && offer.prod == product;
});
};
self.filter = function(keyFilter) {
self.filterKey(keyFilter);
};
(function() {
// Products
self.wrappedProducts.push({"prod":"1"});
self.wrappedProducts.push({"prod":"2"});
// Offers
self.availableOffers.push({"name": "offer1", "key": "1", "prod": 1});
self.availableOffers.push({"name": "offer2", "key": "2", "prod": 1});
self.availableOffers.push({"name": "offer3", "key": "2", "prod": 2});
})();
}
var viewModel = new myViewModel();
ko.applyBindings(viewModel);
ul, h4 {margin-top: 0px; margin-bottom:0px;}
label {font-weight:bold;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h4>All products</h4>
<ul data-bind="foreach: wrappedProducts">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
<h4>Available offers</h4>
<ul data-bind="foreach: availableOffers">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
<div>
<button data-bind="click: function() {filter(1);}">key1</button>
<button data-bind="click: function() {filter(2);}">key2</button>
filterKey = <span data-bind="text: filterKey"></span>
</div>
<hr>
<label for="prod1">Product 1</label>
<ul data-bind="foreach: filteredItems(1)">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
<label for="prod2">Product 2</label>
<ul data-bind="foreach: filteredItems(2)">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
Well, The you were just displaying the results of the same computed. So, that's why under Prod 1 and Prod 2 you were getting the same results. If you want to filter by product and key, you'll need two computed functions that filter that for you.
The code you're missing inside your arrayFilter was this:
var isCorrectKey = offer.key == self.filterKey();
var isCorrectProduct = offer.prod == 2;
return isCorrectKey && isCorrectProduct;
You have to check for product and key.
function myViewModel() {
var self = this;
self.wrappedProducts = ko.observableArray();
self.availableOffers = ko.observableArray();
self.filterKey = ko.observable(1);
filteredItemsKey1 = ko.computed(function () {
return ko.utils.arrayFilter(self.availableOffers(), function (offer) {
var isCorrectKey = offer.key == self.filterKey();
var isCorrectProduct = offer.prod == 1;
return isCorrectKey && isCorrectProduct;
});
});
filteredItemsKey2 = ko.computed(function () {
return ko.utils.arrayFilter(self.availableOffers(), function (offer) {
var isCorrectKey = offer.key == self.filterKey();
var isCorrectProduct = offer.prod == 2;
return isCorrectKey && isCorrectProduct;
});
});
self.filter = function(keyFilter) {
self.filterKey(keyFilter);
};
(function() {
// Products
self.wrappedProducts.push({"prod":"1"});
self.wrappedProducts.push({"prod":"2"});
// Offers
self.availableOffers.push({"name": "offer1", "key": "1", "prod": 1});
self.availableOffers.push({"name": "offer2", "key": "2", "prod": 1});
self.availableOffers.push({"name": "offer3", "key": "2", "prod": 2});
})();
}
var viewModel = new myViewModel();
ko.applyBindings(viewModel);
ul, h4 {margin-top: 0px; margin-bottom:0px;}
label {font-weight:bold;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h4>All products</h4>
<ul data-bind="foreach: wrappedProducts">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
<h4>Available offers</h4>
<ul data-bind="foreach: availableOffers">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
<div>
<button data-bind="click: function() {filter(1);}">key1</button>
<button data-bind="click: function() {filter(2);}">key2</button>
filterKey = <span data-bind="text: filterKey"></span>
</div>
<hr>
<label for="prod1">Product 1</label>
<ul data-bind="foreach: filteredItemsKey1">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>
<label for="prod2">Product 2</label>
<ul data-bind="foreach: filteredItemsKey2">
<li data-bind="text: ko.toJSON($data)"></li>
</ul>

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/

knockout : table in each option in drop-down

I want have table with two columns in each <option> in me <select>.
I tried this JSFIDDLE-source. But bindings in <span> isn't working
<span data-bind="text : countryName"></span>
<span data-bind="text : selectedCountry"></span>
Any ideas? Or another solution?
EDIT
While you can't style the contents of a you can use something like Twitter's excellent Bootstrap framework for a similar effect.
http://jsfiddle.net/marrok/QdjPt/
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<span class="name" data-bind="text: selectedItem().name"></span>
<span class="desc" data-bind="text: selectedItem().desc"></span> <span class="caret"></span>
</a>
<ul class="list dropdown-menu" data-bind="foreach: items ">
<!-- I should be able to use 'click' here but I'm not sure why it doesn't work. -->
<li class="item" data-bind="event: {mouseover : function(){$root.selectedIndex($index())}}">
<span class="name" data-bind="text: name"></span>
<span class="desc" data-bind="text: desc"></span>
</li>
</ul>
</div>
Javascript:
var Thing = function(name, desc) {
var self = this;
self.name = ko.observable(name);
self.desc = ko.observable(desc);
};
var AppModel = function() {
var self = this;
self.items = ko.observableArray([
new Thing(1, "Thing One"),
new Thing(2, "Thing Two"),
new Thing(3, "Thing Three"),
new Thing(4, "Thing Four")]);
self.selectedIndex = ko.observable(0);
self.selectedItem = ko.computed(function() {
return self.items()[self.selectedIndex()];
})
};
ko.applyBindings(new AppModel());​

Categories

Resources