AngularJS checkbox filter - angularjs

I would like to filter the results.
There is a list of wines, my wish is when no checkbox is checked, the entire list of wine is displayed.
when only 1 checkbox is checked is displayed the related category
when more than one checkbox are checked the related categories are displayed
I'm a newbie to AngularJS, I tried with ng-model wihout success, here is the code without ng-model associated to the function:
<html ng-app="exampleApp">
<head>
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.10/angular.min.js"></script>
<script>
angular.module("exampleApp", [])
.controller("defaultCtrl", function ($scope) {
$scope.wines = [
{ name: "Wine A", category: "red" },
{ name: "Wine B", category: "red" },
{ name: "wine C", category: "white" },
{ name: "Wine D", category: "red" },
{ name: "Wine E", category: "red" },
{ name: "wine F", category: "white" },
{ name: "wine G", category: "champagne"},
{ name: "wine H", category: "champagne" }
];
$scope.selectItems = function (item) {
return item.category == "red";
};
$scope.selectItems = function (item) {
return item.category == "white";
};
$scope.selectItems = function (item) {
return item.category == "champagne";
};
});
</script>
</head>
<body ng-controller="defaultCtrl">
<h4>red: <input type="checkbox"></h4>
<h4>white: <input type="checkbox"></h4>
<h4>champagne: <input type="checkbox"></h4>
<div ng-repeat="w in wines | filter:selectItems">
{{w.name}}
{{w.category}}
</div>
</body>
</html>
How to use ng-model or ng-change to associate a function to each checkbox button to have a real time filtering model??

There are several implementations possible. Here's one:
Have a $scope.filter = {} object to hold the state of each filter. E.g. {red: true, white: false...}.
Associate each checkbox with the corresponding property using ng-model. E.g.: input type="checkbox" ng-model="filter['red']" />.
Have a function (e.g. $scope.filterByCategory(wine)) that decides if a wine should be displayed or not (based on the $scope.filter object).
Use that function to filter the items based on their category. E.g. <div ng-repeat="wine in wines | filter:filterByCategory">
The filterByCategory function could be implemented like this:
function filterByCategory(wine) {
// Display the wine if
var displayWine =
// the wine's category checkbox is checked (`filter[category]` is true)
$scope.filter[wine.category] || // or
// no checkbox is checked (all `filter[...]` are false)
noFilter($scope.filter);
return displayWine;
};
where noFilter() is a function that checks if there is any filter activated (and returns true if there is none):
function noFilter(filterObj) {
return Object.
keys(filterObj).
every(function (key) { return !filterObj[key]; });
}
See, also, this short demo.
UPDATE:
I created a modified version, which supports multiple filters (not just filtering by category).
Basically, it dynamically detects the available properties (based on the first wine element), adds controls (groups of check-boxes) for applying filters based on each property and features a custom filter function that:
Filters each wine item, based on every property.
If a property has no filter applied (i.e. no check-box checked), it is ignored.
If a property has check-boxes checked, it is used for filtering out wine items (see above).
There is code for applying multiple filters using AND (i.e. all properties must match) or OR (at least one property must match).
See, also, this updated demo.

Just to add onto #gkalpak answer, I found this codepen which allows you to provide the total amount left after an option is selected for each category.
Change the ng-repeat from:
<div ng-repeat="wine in (ctrl.wines | filter:ctrl.filterByProperties) as filteredWines">
{{ wine.name }} <i>({{ wine.category }})</i>
</div>
To
<div ng-repeat="wine in filtered = (ctrl.wines | filter:ctrl.filterByProperties) as filteredWines">
{{ wine.name }} <i>({{ wine.category }})</i>
</div>
And with the input labels add:
<label>
<input type="checkbox" ng-model="ctrl.filter[prop][value]" />
{{ value }}({{(filtered | filter:value:true).length}})
</label>

I prefer use filter as $filter
app.filter('someFilter',checkboxFilter)
checkboxFilter() {
return function (arr,filter,key,noOne=false) {
// arr is an array of objects
// filter is checkbox filter. someting like {1:true,2:false}
// key is a property in ech object inside arr
// noOne is a behavior if none of checkbox is activated (default:false)
if (!arr.length) return null;
function noOneCheck(filter) {
return Object.keys(filter).every((key) => {
return !filter[key]
})
}
return arr.filter((i) => {
return filter[i[key]] || (noOne && noOneCheck(filter))
})
}
};
html:
ng-repeat="u in project.projectTeamInvite | checkbox:project.status:'status' track by $index">

Related

AngularJS: refresh ng-options when property source object changes

Full description:
I have list of options with multiple properties (not just key-value pair):
[
{
id: 1,
name: "111",
some: "qqq"
},
{
id: 2,
name: "222",
some: "www"
}
];
And select defined as:
<select name="mySelect" id="mySelect"
ng-options="option.name for option in opts track by option.id"
ng-model="mod1"></select>
Depending on app logic, extra property may change:
{
id: 2,
name: "222",
some: "www1"
}
But actual list in Select doesn't change!
However, if name changes, then entire optionList will be refreshed.
In short you can see demo on JSFiddle OR JSFiddle. I prepared 2 very similar examples:
When button is clicked only extra property updates
When button is clicked - both extra property and key receive new value
Does anybody know solution?
UPDATE
For now I'm solving that issue with update + delay + update solution:
this.click = function(){
$scope.opts = {};
$timeout(function() {
$scope.opts = { /* NEW OBJECT */};
}, 0);
}
OK, so I think I understand what you want, which is to be able to select an option whose nested values may have changed since the list was rendered in the DOM.
Based on that understanding, I believe that the plunker I have created illustrates a solution for you. If you select one of the options, and change the child value in the input field, two-way binding will update the model.
Basically, it is taking the users selection, and on select change, re-assigning the selected object to reference the original option in the options array. This will allow two-way binding to occur. If you view the code, you will see that the input fields are updating the option list itself ($scope.options), where-as the model that is being displayed is $scope.formData.model.
https://plnkr.co/edit/DLhI7t7XBw9EbIezBCjI?p=preview
HTML
<select
name="mySelect"
id="mySelect"
ng-model="formData.model"
ng-change="onChange(formData.model)"
ng-options="option.name for option in options track by option.id"></select>
SELECTED CHILD: {{formData.model.child.name}}
<hr>
<div ng-repeat="option in options">
Child Name for {{ option.name }}: <input ng-model="option.child.name">
</div>
JS
$scope.onChange = function(option) {
angular.forEach($scope.options,function(optionItem){
if (optionItem.id == option.id){
$scope.formData.model = optionItem;
}
})
}
$scope.options = [
{
id: 1,
name: "111",
child: {
id: 11,
name: "111-1"
}
},
{
id: 2,
name: "222",
child: {
id: 22,
name: "222-1"
}
}
];
$scope.formData = {
model: $scope.options[0]
};
Call $apply whenever you want to apply changes made.
$scope.$apply();
This will tell AngularJS to refresh.

AngularJS - radio checked based on object value

What I'm trying to do is have a service (lets say: myService) that holds specific data like objects representing printers present and selected:
var localPrinters = [{ id: 12, name: 'HP', type: 'laser' },
{ id: 33, name: 'Lexmark', type: 'laser' }];
var activePrinter = {};
In some view that gets shown occasionally (like app settings), I have a controller that would define variables in the local scope which would point to the objects in the injected myService.
The view would then use ng-repeat to iterate over printer objects in localPrinters and display radio buttons that correspond to each object in the array..
Now i need two things..
1) update the activePrinter upon radiobutton selection change with the corresponding object value
2) in case the activePrinter already contains an object, when the view loads i want the corresponding radio to be checked already (if its value object matches the object in activePrinter, otherwise none should be selected.
I've managed 1) in a couple of ways.. either sticking to the model usage or adding methods to call upon ng-change.
//pseudocode
<container ng-repeat="printer in printers" >
<radio ng-value="printer" ng-model="$scope.activePrinter"/>
</container>
or
//pseudocode
<container ng-repeat="printer in printers" >
<radio ng-value="printer" ng-change="selectPrinter(printer)" "ng-model="$scope.activePrinter"/>
</container>
What i'm having trouble with is 2)
Not sure if there's a way in angular to automatically figure out some of the printer values matches the activePrinter selection and make the radio checked. Also not sure of the way i'm using ng-model for this purpose.
Any pointers?
Thanks!
You can do that in this way:
var app = angular.module('app', []);
app.controller('firstCtrl', function($scope) {
$scope.printers = [{
id: 12,
name: 'HP',
type: 'laser'
}, {
id: 33,
name: 'Lexmark',
type: 'laser'
}];
$scope.activePrinter = {};
//set default printer
$scope.activePrinter.printer = $scope.printers[0]
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="firstCtrl">
<div class="container" ng-repeat="printer in printers">
<label>{{printer.name}}</label>
<input type="radio" ng-value="printer" ng-model="activePrinter.printer" />
</div>
Active Printer:{{activePrinter.printer.id}} | {{activePrinter.printer.name}} | {{activePrinter.printer.type}}
</div>
</body>

Disable check-boxes using a function with AngularJS

I have a question about how to uncheck check-boxes inside ng-repeat, when the ng-model is already in use?
This is the construction:
The object:
$scope.wines = [
{ "name": "Wine a", "type": "white", "country": "italie", "style": "medium" },
{ "name": "Wine a", "type": "white", "country": "france", "style": "light" },
{ "name": "Wine a", "type": "white", "country": "france", "style": "sweet" }
];
$scope.winetypes = {white : true, red: true};
$scope.whitetypes = {light : false,medium : false,sweet : false};
});
HTML
<li ng-repeat="(type, value) in winetypes">
<input type="checkbox" ng-model="winetypes[type]" /> {{type}}
</li>
<li ng-repeat="(style, value) in whitetypes">
<input type="checkbox" ng-model="whitetypes[style]" /> {{style}}
</li>
<ul>
<li ng-repeat="wine in wines | winetypefilter:winetypes |whitefilter:whitetypes">
{{wine.name}} is a {{wine.type}} with {{wine.style}} style from {{wine.country}}
</li>
</ul>
My wish: the check-boxes linked to the whitetypes (light, medium, sweet) would be automatically unchecked, when the white check-box would be unchecked. I guess ng-model can't be used to achieve my wishes, because it's already in use.
I tried without success:
$scope.white= function() {
if($scope.winetypes.white = false) {
return $scope.whitetypes = {light: false, medium: false, sweet: false}
};
$scope.white;
The demo: http://plnkr.co/edit/nIQ2lkiJJY9MwJKHrqOk?p=preview
first decide what action you want...
You want to change a checkbox model and it's value should effect
other checkbox values...
so first solutions that come to my minds are...
ng-change (because your wish is about changing a checkbox attribute)
ng-click (for changing checkbox attribute you should click that input)
ng-checked (set condition to be checked or unchecked)
ok let's move on our solution... After analysing these three (there can be more solutions) ng-change is best for this scenario because it guarantees that binded fucntion will be execute after user changed value of checkbox... for more details check official docs
first edit your html...
<li ng-repeat="(type, value) in winetypes">
<input type="checkbox" ng-model="winetypes[type]" ng-change="disableRelatedStyles(type, winetypes[type])"/> {{type}}
</li>
and add our new function (disableRelatedStyles) to our controller...
$scope.disableRelatedStyles = function (type, value) {
if (type == 'white' && !value) {
for(var style in $scope.whitetypes) {
$scope.whitetypes[style] = false;
}
}
};
and finally a working PLUNKER...
What you could do is use ng-click and $timeout to handle every click to the white checkbox.
$scope.click_handler = function (type) {
if (type == 'white') {
$timeout(function () {
if ($scope.winetypes[type] === false) {
$scope.wines.forEach(function (e) {
if (e.type == 'white') {
$scope.whitetypes[e.style] = false;
}
})
}
})
}
};
The $timeout is necessary since we want to wait for ng-model to update first. Make sure you are injecting $timeout into the controller.
in markup
<input type="checkbox" ng-click="click_handler(type)" ng-model="winetypes[type]" /> {{type}}
Here is a working Plunker

AngularJS: Binding boolean value to radio button such that it updates model to false on uncheck event

In my AngularJS application, I am displaying contacts data in a grid. My typical contacts JSON looks like as below ...
[
{ type: "IM", value: "mavaze123", default: true },
{ type: "IM", value: "mvaze2014", default: false },
{ type: "IM", value: "mavaze923", default: false },
{ type: "IM", value: "mvaze8927", default: false },
{ type: "Email", value: "mavaze123#abc.com", default: true },
{ type: "Email", value: "mvaze2014#xyz.net", default: false }
]
The last property 'default' is actually a radio button, selection of which should alter the original default value of the corresponding contact type in above JSON. There can be one default from each type of contact i.e. we can group radio buttons based on the contact type.
<div ng-repeat="contact in contacts">
<div>{{contact.type}}</div>
<div>{{contact.value}}</div>
<div><input type="radio" name="{{contact.type}}" ng-model="contact.default" ng-value="true"/></div>
</div>
Note: The above code is not the exact one, but approximately same, as
it will appear inside a custom grid component.
Now when I load my view/edit form page with above JSON, it correctly shows the radio state of all contacts. The problem comes, after page load, when user selects another contact as default. This actually changes the model value of default to true for newly selected contact however the model value of original default contact still remains true, even though its radio state changes to uncheck/blur (because they are having same 'name' value).
I thought to write a directive, but I am unable get it triggered on radio on-blur/uncheck event.
There are various posts on binding boolean values to radio buttons, but I am unable to get it work in my scenario, as I want to update model values for individual radio button in a radio group. See there is no single model representing a radio group.
I think you should change your design to separate the contacts from contactTypes and store the key to the default contact in contact type.
In your current design, there are duplicated values for default and that's not the desired way to work with radio.
$scope.contacts = [
{ type: "IM", value: "mavaze123" },
{ type: "IM", value: "mvaze2014" },
{ type: "IM", value: "mavaze923" },
{ type: "IM", value: "mvaze8927" },
{ type: "Email", value: "mavaze123#abc.com" },
{ type: "Email", value: "mvaze2014#xyz.net" }
];
$scope.contactTypes = {
"IM": { default:"mavaze123"}, //the default is contact with value = mavaze123
"Email": { default:"mavaze123#abc.com"}
};
You Html:
<div ng-repeat="contact in contacts">
<div>{{contact.type}}</div>
<div>{{contact.value}}</div>
<div><input type="radio" name="{{contact.type}}" ng-model="contactTypes[contact.type].default" ng-value="contact.value"/></div>
</div>
DEMO
I assume that the key of contact is value, you could use an Id for your contact.
I added an attribute directive in my input statement ...
<div ng-repeat="contact in contacts">
<div>{{contact.type}}</div>
<div>{{contact.value}}</div>
<div><input type="radio" name="{{contact.type}}" ng-model="contact.default" ng-value="true" boolean-grid-model /></div>
</div>
And my custom directive ...
myModule.directive('booleanGridModel') {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attrs, controller) {
var radioSelected = scope.$eval(attrs.ngModel);
if(radioSelected) {
var selectedContact = scope.contact;
_.each(scope.contacts, function(contact) {
if(contact.type === selectedContact.type) {
_.isEqual(contact, selectedContact) ? contact.default = true : contact.default = false;
}
});
}
}
};
}
WHy you declare ng-value="true" please remove that
<div><input type="radio" name="{{contact.type}}" ng-model="contact.default" ng-value="{{contact.default}}"/></div>
Please use $scope.$apply() in your value changing code
Like something below
$scope.$apply(function ChangeType()
{
/Code
});
And you need to change name="{{contact.type}}" to name="contact.type{{$index}}" Because some types are same name.

Dynamic filter within ng-repeat in AngularJS

For my AngularJS app I have an ng-repeat in an ng-repeat like so:
Html:
<div ng-app="myApp">
<div ng-controller="myController">
<h2>working filter</h2>
<div ng-repeat="category in categories">
<h3>{{category}}</h3>
<div ng-repeat="item in items | filter:{price.red: 0} ">{{item.name}}</div>
</div>
<h2>broken filter</h2>
<div ng-repeat="category in categories">
<h3>{{category}}</h3>
<!-- price[category] should be red, blue, yellow, depending on the category in the ng-repeat -->
<!-- price[category] should be bigger then 0 (or not zero, negative values will not occur) -->
<div ng-repeat="item in items | filter:{price[category]: 0} ">{{item.name}}</div>
</div>
</div>
</div>
Javascript:
var myApp = angular.module('myApp', []);
myApp.controller('myController', function ($scope) {
$scope.categories = ['red', 'blue', 'yellow'];
$scope.items = [
{name: 'one', price:{red: 0, blue: 1, yellow: 3} },
{name: 'two', price:{red: 2, blue: 0, yellow: 0}},
]
});
Please see my jsFiddle http://jsfiddle.net/hm8qD/
So I ran into two problems:
The flter does not accept [] brackets so I cannot make the filter dynamic, based on the category
The filter needs to return items that have a price[category] greater then zero.
For the greater then zero I could just make a custom filter. However as far as I know filters don't accept parameters so I have no way of getting the category (red, blue or yellow) to it.
Please note this is an simplified version of my actual code and this context might not make the best of sense. I hope I was clear in explaining what I need the filter to do, since I'm new to AngularJS.
Apparently it is actually possible to pass arguments to your filter, but you have to make a custom filter instead of using "filter: expression". What I did was create a custom filter which takes the items and category as arguments and returns the array with filtered items.
myApp.filter('myFilter', function () {
return function (items, category) {
var newItems = [];
for (var i = 0; i < items.length; i++) {
if (items[i].price[category] > 0) {
newItems.push(items[i]);
}
};
return newItems;
}
});
See the updated Fiddle here: http://jsfiddle.net/hm8qD/3/
I found a different and quite neat solution for sending parameters to the filter (written in es6):
$scope.myFilter = (category) => {
return (item) => {
if(category === 'fish' && item.type === 'salmon'){
return true
}
return false
}
}
the markup would be:
<div ng-repeat="item in items |
filter:myFilter(someCategory)>
{{item.name}}
</div>
So basically you can just scope the "category" into the filter as myFilter returns a function on the format expected by the filter.

Resources