Angular js comparison - javascript

I have a condition that needs to be checked in my view: If any user in the user list has the same name as another user, I want to display their age.
Something like
<div ng-repeat="user in userList track by $index">
<span class="fa fa-check" ng-if="user.isSelected"></span>{{user.firstName}} <small ng-if="true">{{'AGE' | translate}} {{user.age}}</small>
</div>
except I'm missing the correct conditional

You should probably run some code in your controller that adds a flag to the user object to indicate whether or not he/she has a name that is shared by another user.
You want to minimize the amount of logic there is inside of an ng-repeat because that logic will run for every item in the ng-repeat each $digest.
I would do something like this:
controller
var currUser, tempUser;
for (var i = 0; i < $scope.userList.length; i++) {
currUser = $scope.userList[i];
for (var j = 0; j < $scope.userList.length; j++) {
if (i === j) continue;
var tempUser = $scope.userList[j];
if (currUser.firstName === tempUser.firstName) {
currUser.showAge = true;
}
}
}
html
ng-if='user.showAge'
Edit: actually, you probably won't want to do this in the controller. If you do, it'll run every time your controller loads. You only need this to happen once. To know where this should happen, I'd have to see more code, but I'd think that it should happen when a user is added.

You can simulate a hashmap key/value, and check if your map already get the property name. Moreover, you can add a show property for each objects in your $scope.userList
Controller
(function(){
function Controller($scope) {
var map = {};
$scope.userList = [{
name:'toto',
age: 20,
show: false
}, {
name:'titi',
age: 22,
show: false
}, {
name: 'toto',
age: 22,
show: false
}];
$scope.userList.forEach(function(elm, index){
//if the key elm.name exist in my map
if (map.hasOwnProperty(elm.name)){
//Push the curent index of the userList array at the key elm.name of my map
map[elm.name].push(index);
//For all index at the key elm.name
map[elm.name].forEach(function(value){
//Access to object into userList array with the index
//And set property show to true
$scope.userList[value].show = true;
});
} else {
//create a key elm.name with an array of index as value
map[elm.name] = [index];
}
});
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
HTML
<body ng-app="app" ng-controller="ctrl">
<div ng-repeat="user in userList track by $index">
<span class="fa fa-check"></span>{{user.name}} <small ng-if="user.show">{{'AGE'}} {{user.age}}</small>
</div>
</body>

Related

WishList boolean toggle button toggles all items in ng-repeat instead of single item

Within a ng-repeat="item in items" I have a Wishlist boolean button which can be true or false.
<div ng-repeat="item in items" ng-controller="ItemCtrl as ctrl">
<md-button class="md-icon-button" ng-click="ctrl.toggleWish(item)">
<i class="material-icons">{{ctrl.hasWished(item) ? 'favorite' : 'favorite_border' }}</i>
</md-button>
</div>
with the hasWished(item) function, I check if the currently logged in User id (Auth._currentUser.id) is within the user ids stored within the wishlist of the item.
The JSON output of a single item looks simplified like this:
{"id":1,"title":"This is a tile","wishes":2,"wishlists":[{"user_id":2},{"user_id":3}]}
So the hasWished(item) function should return true or false:
this.hasWished = function(item) {
return item.wishlists.some(function(wishlist) {
return wishlist.user_id === Auth._currentUser.id // returns true if currently logged in user id is on wishlist
});
};
That is working so far. With the toggleWish function I want to toggle the hasWished(item) from true to false, or vice versa:
this.toggleWish = function(item) {
if (!this.hasWished(item)) {
this.hasWished = function(item){return true};
items.wish(item); // sends PUT request to API
item.wishes += 1;
ToastService.show(item.title + ' added to Wish List');
} else {
this.hasWished = function(item){return false};
items.unWish(item); // sends DELETE request to API
item.wishes -= 1;
ToastService.show(item.product + ' removed from Wish List');
}
};
Currently toggleWish does toggle, but toggles all items within ng-repeat="item in items" to true or false (depending if they were true or false before). instead of only the single item, where the toggleWish(item) button was clicked.
I need to somehow set hasWished for each single item to true or false - independent of the other items, while still checking if the user has wished this item previously (with the current hasWished function).
I am stuck here and I appreciate your advise!
this.hasWished = function(item){return true};
is a function in the controller, so it is not something specific for an item.
You should store a flag in the item. Something like
this.toggleWish = function(item) {
if (!this.hasWished(item)) {
items.wish(item); // sends PUT request to API
item.hasWished = true;
item.wishes += 1;
ToastService.show(item.title + ' added to Wish List');
} else {
items.unWish(item); // sends DELETE request to API
itme.hasWished false;
item.wishes -= 1;
ToastService.show(item.product + ' removed from Wish List');
}
};
and then in the html use that flag to show or not
{{item.hasWished ? 'favorite' : 'favorite_border' }}
After you fetch your JSON, compute flags that your template can use.
Then toggle those flags.
HTML
<div ng-repeat="item in items" ng-controller="ItemCtrl as ctrl">
<md-button class="md-icon-button" ng-click="ctrl.toggleWishFlag($index)">
<i class="material-icons">
{{ctrl.flags.wish[$index] ? 'favorite' : 'favorite_border' }}
</i>
</md-button>
</div>
Compute your flags:
var self = this;
this.computeFlags = function(items) {
self.flags = self.flags || {};
self.flags.wish = [];
for (var i=0; i<items.length; i++) {
self.flags.wish.push(
self.hasWished(items[i]);
);
};
};
Toggle your flags:
this.toggleWishFlag = function(index) {
var item = items(index);
if (!self.flags.wish[index]) {
self.flags.wish[index] = true;
// send PUT request to API
ToastService.show(item.title + ' added to Wish List');
} else {
self.flags.wish[index] = false;
// send DELETE request to API
ToastService.show(item.product + ' removed from Wish List');
}
};
Notice my use of the $index special property in the HTML. For more information on that see the AngularJS ngRepeat API Docs.

How to group all product of single brand together - angular JS

Please see this JS fiddle link.
http://jsfiddle.net/4Dpzj/174/
This is the logic for group by
app.filter('groupBy', ['$parse', function ($parse) {
return function (list, group_by) {
var filtered = [];
var prev_item = null;
var group_changed = false;
// this is a new field which is added to each item where we append "_CHANGED"
// to indicate a field change in the list
//was var new_field = group_by + '_CHANGED'; - JB 12/17/2013
var new_field = 'group_by_CHANGED';
// loop through each item in the list
angular.forEach(list, function (item) {
group_changed = false;
// if not the first item
if (prev_item !== null) {
// check if any of the group by field changed
//force group_by into Array
group_by = angular.isArray(group_by) ? group_by : [group_by];
//check each group by parameter
for (var i = 0, len = group_by.length; i < len; i++) {
if ($parse(group_by[i])(prev_item) !== $parse(group_by[i])(item)) {
group_changed = true;
}
}
}// otherwise we have the first item in the list which is new
else {
group_changed = true;
}
// if the group changed, then add a new field to the item
// to indicate this
if (group_changed) {
item[new_field] = true;
} else {
item[new_field] = false;
}
filtered.push(item);
prev_item = item;
});
return filtered;
};
I want to group all the products together.
what changes i need to do ?
I come up with this in my mind. Without using any custom filters.
I simply use this ng-repeat syntax :
ng-repeat="(key,item) in MyList | orderBy:orderKey"
Thanks to it i can get the key to compare the value with the previous object.
Here is my ng-show attribute. It can be improved by sorting the list somewhere else (like in the controller)
<h2 ng-show="(MyList | orderBy:orderKey)[key-1][orderKey] !== (MyList | orderBy:orderKey)[key][orderKey]"
Thanks to this you can populate your var "orderKey" with any of your attribute name and this will works.
See it working in this JSFiddle
Hope it helped.
EDIT :
I think it would be a bit cleaner to use a temporary list to manage the visual order (see it in this JSFiddle):
JS :
$scope.orderList = function(){
$scope.orderedList = $filter('orderBy')($scope.MyList,$scope.orderKey);
}
HTML :
ng-change="orderList()" To trigger the list sort
The cleaner ng-repeat / ng-show
<div ng-repeat="(key,item) in orderedList">
<h2 ng-show="orderedList[key-1][orderKey] !== orderedList[key][orderKey]">{{item[orderKey]}} </h2>
<ul>
<li>{{item.ProductName}}</li>
</ul>
</div>
Have a look at this:
http://jsfiddle.net/4Dpzj/176/
<div ng-repeat="item in MyList | orderBy:['SubCategoryName','BrandName'] | groupBy:['SubCategoryName']" >
<h2 ng-show="item.group_by_CHANGED">{{item.SubCategoryName}} </h2>
<ul>
<li>{{item.ProductName}} --- {{item.BrandName}}</li>
</ul>
</div>

Filter ng-repeat with dropdown without duplicating the dropdown options

The same way, I can manually do filter: { category : 'Popular'} in ng-repeat, I'd like to be able to do the same thing with the dropdown.
I was able to make the basics work. I have two problems: I don't want the categories to duplicate themselves in the dropdown, I'd like to be able to see everything categorized "Popular" when I select "Popular" in the dropdown.
Here is my HTML:
<div ng-controller="SuperCtrl" class="row">
<ul class="small-12 medium-12 columns">
<select ng-model="find" ng-options="entry.category for entry in parsedEntries"><option value="">Select Category</option></select>.
<li ng-repeat="entry in parsedEntries | filter: find">
<strong>{{ entry.title }} </strong><br>
{{ entry.description }}
</li>
</ul></div>
Here is the controller:
app.controller('SuperCtrl', ['$scope', '$http', function($scope,$http) {
var url = 'https://spreadsheets.google.com/feeds/list/1lZWwacSVxTD_ciOsuNsrzeMTNAl0Dj8SOrbaMqPKM7U/od6/public/values?alt=json'
var parse = function(entry) {
var category = entry['gsx$category']['$t'];
var description = entry['gsx$description']['$t'];
var title = entry['gsx$title']['$t'];
return {
category: category,
description: description,
title: title
};
}
$http.get(url)
.success(function(response) {
var entries = response['feed']['entry'];
$scope.parsedEntries = [];
for (key in entries) {
var content = entries[key];
$scope.parsedEntries.push(parse(content));
}
});
}]);
Got it working as you want with :
<select ng-model="find" ng-options="entry.category as entry.category for entry in parsedEntries | unique: 'category'">
The unique filter is from angular-filter. It requires to add 'angular.filter' you to your modules dependencies:
var app = angular.module('myApp', ['angular.filter']);
See fiddle
NB: Not a problem by itself but I took the <select> element out of the <ul> one.
Just put unique categories into in a string array called categories, sort the array, and display it with ng-options:
<select ng-model="find" ng-options="category as category for category in categories"><option value="">Select Category</option></select>.
Append this to your code after your parse function, and delete the $http.get you had. This defines a contains function and builds the array at the same time the objects come back:
function contains(a, obj) {
for (var i = 0; i < a.length; i++) {
if (a[i] === obj) {
return true;
}
}
return false;
};
$http.get(url)
.success(function(response) {
var entries = response['feed']['entry'];
$scope.parsedEntries = [];
$scope.categories = [];
for (key in entries) {
var content = entries[key];
var obj = parse(content);
$scope.parsedEntries.push(obj);
if (!contains($scope.categories, obj.category))
{
$scope.categories.push(obj.category);
}
}
$scope.categories.sort();
})

Show/hide a button inside ng-repeat

I am trying to show/hide buttons on a ng-repeat (a simple table). A delete button replaced by a conform button.
Here is my code
..... Angular stuff .....
function ContactsCtrl($scope, $http) {
$scope.order = '-id';
$scope.currentPage = 0;
$scope.pageSize = 15;
$http.get('/events/<%= #event.id -%>/contacts.json').success(function(data) {
$scope.contacts = data;
$scope.numberOfPages=function(){
return Math.ceil($scope.contacts.length/$scope.pageSize);
}
});
$scope.clickDelete = function(e,t) {
console.log("delete");
// rest api stuff...
$scope.contacts.splice(e, 1); // This WORKS!
};
$scope.showDelete = function(e,t) {
e.showDeleteButton = true; // This DOES NOT
};
}
And in HTML:
<tr ng-repeat="contact in contacts | filter:search | orderBy:order | startFrom:currentPage*pageSize | limitTo:pageSize">
<td>{{contact.email}}</td>
...
<td>delete
confirm
</td>
</tr>
You don't appear to be returning a value from the showDelete function. It also looks like there is a property on the JSON object 'showDeleteButton' which you could bind to directly.
Example plnkr: http://plnkr.co/edit/eZTFyw9tGeWEfYw0U0I8
It seems like what you are trying to do is have the delete button just set a flag that will show the confirm button which will actually perform the delete, correct? ng-repeat creates a new child scope for each element, so you could just set a 'confirmable' flag on the child scope and use that (fiddle):
<a ng-click="confirmable = true">delete</a>
<a ng-show="confirmable" ng-click="clickDelete(contact)">confirm</a>
<a ng-show="confirmable" ng-click="confirmable = false">cancel</a>
Also it looks like you're passing the contact object to your clickDelete function and using it as an index into the array so I don't know why that works. The fiddle uses indexOf to find the index to delete.
This is how I did it:
JavaScript:
$scope.clickDelete = function(contact,i) {/* ... */ $scope.contacts.splice(i, 1);};
$scope.clickShowConfirm = function(contact) {contact.showdelete = true;};
$scope.clickCancel = function(contact) {contact.showdelete = false;}
$scope.showOrHide = function(contact) {return contact.showdelete;};
HTML:
delete
ok
cancel

Filter users by one keyword in a nested observableArray

I am trying to filter my users observableArray which has a nested keywords observableArray
based on a keywords observableArray on my viewModel.
When I try to use ko.utils.arrayForEach I get a stack overflow exception. See the code below, also posted in this jsfiddle
function User(id, name, keywords){
return {
id: ko.observable(id),
name: ko.observable(name),
keywords: ko.observableArray(keywords),
isVisible: ko.dependentObservable(function(){
var visible = false;
if (viewModel.selectedKeyword() || viewModel.keywordIsDirty()) {
ko.utils.arrayForEach(keywords, function(keyword) {
if (keyword === viewModel.selectedKeyword()){
visible = true;
}
});
if (!visible) {
viewModel.users.remove(this);
}
}
return visible;
})
}
};
function Keyword(count, word){
return{
count: ko.observable(count),
word: ko.observable(word)
}
};
var viewModel = {
users: ko.observableArray([]),
keywords: ko.observableArray([]),
selectedKeyword: ko.observable(),
keywordIsDirty: ko.observable(false)
}
viewModel.selectedKeyword.subscribe(function () {
if (!viewModel.keywordIsDirty()) {
viewModel.keywordIsDirty(true);
}
});
ko.applyBindings(viewModel);
for (var i = 0; i < 500; i++) {
viewModel.users.push(
new User(i, "Man " + i, ["Beer", "Women", "Food"])
)
}
viewModel.keywords.push(new Keyword(1, "Beer"));
viewModel.keywords.push(new Keyword(2, "Women"));
viewModel.keywords.push(new Keyword(3, "Food"));
viewModel.keywords.push(new Keyword(4, "Cooking"));
And the View code:
<ul data-bind="template: { name: 'keyword-template', foreach: keywords }"></ul><br />
<ul data-bind="template: { name: 'user-template', foreach: users }"></ul>
<script id="keyword-template" type="text/html">
<li>
<label><input type="radio" value="${word}" name="keywordgroup" data-bind="checked: viewModel.selectedKeyword" /> ${ word }<label>
</li>
</script>
<script id="user-template" type="text/html">
<li>
<span data-bind="visible: isVisible">${ $data.name }</span>
</li>
</script>
Your isVisible dependentObservable has created a dependency on itself and is recursively trying to evaluate itself based on this line:
if (!visible) {
viewModel.users.remove(this);
}
So, this creates a dependency on viewModel.users, because remove has to access the observableArray's underlying array to remove the user. At the point that the array is modified, subscribers are notified and one of the subscribers will be itself.
It is generally best to not change the state of any observables in a dependentObservable. you can manually subscribe to changes to a dependentObservable and makes your changes there (provided the dependentObservable does not depend on what you are changing).
However, in this case, I would probably instead create a dependentObservable at the viewModel level called something like filteredUsers. Then, return a version of the users array that is filtered.
It might look like this:
viewModel.filteredUsers = ko.dependentObservable(function() {
var selected = viewModel.selectedKeyword();
//if nothing is selected, then return an empty array
return !selected ? [] : ko.utils.arrayFilter(this.users(), function(user) {
//otherwise, filter on keywords. Stop on first match.
return ko.utils.arrayFirst(user.keywords(), function(keyword) {
return keyword === selected;
}) != null; //doesn't have to be a boolean, but just trying to be clear in sample
});
}, viewModel);
You also should not need the dirty flag, as dependentObservables will be re-triggered when any observables that they access have changed. So, since it accesses selectedKeyword, it will get re-evaluated whenever selectedKeyword changes.
http://jsfiddle.net/rniemeyer/mD8SK/
I hope that I properly understood your scenario.

Categories