I'm trying to get update the total as the checkboxes are selected and unselected (incase the user changes their mind and no longer wants the item). But I'm not sure how to get the actual value I have assigned to each toy. For example: If the user selects toy 1 and 3 they should see: You ordered Toy1, Toy3 and your total is $6.00. For now I assigned the values with the names themselves which isn't what I want but I just put that to show what I'm trying to do. Is their something else I should be using actually perform an operation to get the total?
<script>
let toylist = [];
let toynames = [
'Toy1 5.00',
'Toy2 5.00',
'Toy3 1.00',
];
function join(toylist) {
return toylist.join(', ');
}
</script>
{#each toynames as toy}
<label>
<input type=checkbox bind:group={toylist} value={toy}> {toy}
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {toylist.join(', ')} and your total is
</p>
{/if}
Ok, first you should separate the toynames array into an array of names and values. Then you should bind to the checked property of the input.
In order to display the current state to the user, we need a reactive declaration. Let's call it total. It contains two functions. The first one gives back an array of the selected names, the second the sum of the selected values.
Both work by looking at the selected property of the object in the toylist array. This updates due to our binding of the checked attribute. I created a repl for you to toy ;-) around with
<script>
let toylist = [
{name: "Toy", value: 5, selected: false},
{name: "Elephant", value: 6, selected: false},
{name: "Choo-Choo", value: 1, selected: false}
]
$: total = {
names: toylist.reduce((acc, cV) => {
if (cV && cV.selected) {
return [...acc, cV.name];
} else return [...acc];
}, []),
values: toylist.reduce((acc, cV) => {
if (cV && cV.selected) {
return parseInt(acc) + parseInt(cV.value);
} else return parseInt(acc);
}, 0)
};
</script>
{#each toylist as {name,value, selected}}
<label>
<input type=checkbox bind:checked={selected} bind:value={value}> {name}
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {total.names.length < 1 ? "nothing": total.names} and your total is {total.values}
</p>
{/if}
EDIT:
Here is the total function with a more classic syntax:
$: total = {
names: toylist.reduce(function(acc, cV) {
if (cV && cV.selected) {
return [...acc, cV.name];
} else return [...acc];
}, []),
values: toylist.reduce(function(acc, cV) {
if (cV && cV.selected) {
return parseInt(acc) + parseInt(cV.value);
} else return parseInt(acc);
}, 0)
};
And here without the ? operator:
<script>
function renderNames() {
if (total.names.length < 1) {
return "nothing";
} else {
return total.names;
}
}
</script>
<p>You ordered {renderNames()} and your total is {total.values}</p>
The best way to isolate the price of each toy would be to make your array of toys into an array of objects where 'name' is one key value pair and price another. For manipulating the data it would be helpful if each toy had an id, and I've added a 'selected' boolean value to each toy that is updated if they are added or removed from the "toylist". I've also added a 'total' variable to hold the total of selected toys prices.
I have played with your code a bit to make this work. I have used buttons instead of checkboxes but you could do it in any way you like. So give this code a go and it should be doing what you want.
<script>
let toylist = [];
let toys = [
{id: 1, name: 'Toy 1', price: 5.00, selected: false},
{id: 2, name: 'Toy 2', price: 5.00, selected: false},
{id: 3, name: 'Toy 3', price: 1.00, selected: false}
];
let total = 0.00
function join(toy) {
if(toy.selected === false) {
toylist = [...toylist, toy.name]
total = total+toy.price
let i = toys.findIndex(t => t.id === toy.id)
toys[i].selected = true
toys = toys
} else {
total = total-toy.price
let i = toys.findIndex(t => t.id === toy.id)
let i2 = toylist.findIndex(t => t === toy.name)
toylist.splice(i2, 1)
toylist = toylist
toys[i].selected = false
toys = toys
}
}
</script>
{#each toys as toy}
<label>
{toy.name}: ${toy.price} <button on:click="{() => join(toy)}" value={toy.name}>{toy.selected ? 'remove' : 'add'}</button>
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {toylist} and your total is ${total}
</p>
{/if}
I want to use the filter in angular and want to filter for multiple values, if it has either one of the values then it should be displayed.
I have for example this structure:
An object movie which has the property genres and I want to filter for Action and Comedy.
I know I can do filter:({genres: 'Action'} || {genres: 'Comedy'}), but what to do if I want to filter it dynamically. E.g. filter: variableX
How do I set variableX in the $scope, when I have an array of the genres I have to filter?
I could construct it as a string and then do an eval() but I don't want to use eval()...
I would just create a custom filter. They are not that hard.
angular.module('myFilters', []).
filter('bygenre', function() {
return function(movies,genres) {
var out = [];
// Filter logic here, adding matches to the out var.
return out;
}
});
template:
<h1>Movies</h1>
<div ng-init="movies = [
{title:'Man on the Moon', genre:'action'},
{title:'Meet the Robinsons', genre:'family'},
{title:'Sphere', genre:'action'}
];" />
<input type="checkbox" ng-model="genrefilters.action" />Action
<br />
<input type="checkbox" ng-model="genrefilters.family" />Family
<br />{{genrefilters.action}}::{{genrefilters.family}}
<ul>
<li ng-repeat="movie in movies | bygenre:genrefilters">{{movie.title}}: {{movie.genre}}</li>
</ul>
Edit here is the link: Creating Angular Filters
UPDATE: Here is a fiddle that has an exact demo of my suggestion.
You can use a controller function to filter.
function MoviesCtrl($scope) {
$scope.movies = [{name:'Shrek', genre:'Comedy'},
{name:'Die Hard', genre:'Action'},
{name:'The Godfather', genre:'Drama'}];
$scope.selectedGenres = ['Action','Drama'];
$scope.filterByGenres = function(movie) {
return ($scope.selectedGenres.indexOf(movie.genre) !== -1);
};
}
HTML:
<div ng-controller="MoviesCtrl">
<ul>
<li ng-repeat="movie in movies | filter:filterByGenres">
{{ movie.name }} {{ movie.genre }}
</li>
</ul>
</div>
Creating a custom filter might be overkill here, you can just pass in a custom comparator, if you have the multiples values like:
$scope.selectedGenres = "Action, Drama";
$scope.containsComparator = function(expected, actual){
return actual.indexOf(expected) > -1;
};
then in the filter:
filter:{name:selectedGenres}:containsComparator
Here is the implementation of custom filter, which will filter the data using array of values.It will support multiple key object with both array and single value of keys. As mentioned inangularJS API AngularJS filter Doc supports multiple key filter with single value, but below custom filter will support same feature as angularJS and also supports array of values and combination of both array and single value of keys.Please find the code snippet below,
myApp.filter('filterMultiple',['$filter',function ($filter) {
return function (items, keyObj) {
var filterObj = {
data:items,
filteredData:[],
applyFilter : function(obj,key){
var fData = [];
if (this.filteredData.length == 0)
this.filteredData = this.data;
if (obj){
var fObj = {};
if (!angular.isArray(obj)){
fObj[key] = obj;
fData = fData.concat($filter('filter')(this.filteredData,fObj));
} else if (angular.isArray(obj)){
if (obj.length > 0){
for (var i=0;i<obj.length;i++){
if (angular.isDefined(obj[i])){
fObj[key] = obj[i];
fData = fData.concat($filter('filter')(this.filteredData,fObj));
}
}
}
}
if (fData.length > 0){
this.filteredData = fData;
}
}
}
};
if (keyObj){
angular.forEach(keyObj,function(obj,key){
filterObj.applyFilter(obj,key);
});
}
return filterObj.filteredData;
}
}]);
Usage:
arrayOfObjectswithKeys | filterMultiple:{key1:['value1','value2','value3',...etc],key2:'value4',key3:[value5,value6,...etc]}
Here is a fiddle example with implementation of above "filterMutiple" custom filter.
:::Fiddle Example:::
If you want to filter on Array of Objects then you can give
filter:({genres: 'Action', key :value }.
Individual property will be filtered by particular filter given for that property.
But if you wanted to something like filter by individual Property and filter globally for all properties then you can do something like this.
<tr ng-repeat="supp in $data | filter : filterObject | filter : search">
Where "filterObject" is an object for searching an individual property and "Search" will search in every property globally.
~Atul
I've spent some time on it and thanks to #chrismarx, I saw that angular's default filterFilter allows you to pass your own comparator. Here's the edited comparator for multiple values:
function hasCustomToString(obj) {
return angular.isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
}
var comparator = function (actual, expected) {
if (angular.isUndefined(actual)) {
// No substring matching against `undefined`
return false;
}
if ((actual === null) || (expected === null)) {
// No substring matching against `null`; only match against `null`
return actual === expected;
}
// I edited this to check if not array
if ((angular.isObject(expected) && !angular.isArray(expected)) || (angular.isObject(actual) && !hasCustomToString(actual))) {
// Should not compare primitives against objects, unless they have custom `toString` method
return false;
}
// This is where magic happens
actual = angular.lowercase('' + actual);
if (angular.isArray(expected)) {
var match = false;
expected.forEach(function (e) {
e = angular.lowercase('' + e);
if (actual.indexOf(e) !== -1) {
match = true;
}
});
return match;
} else {
expected = angular.lowercase('' + expected);
return actual.indexOf(expected) !== -1;
}
};
And if we want to make a custom filter for DRY:
angular.module('myApp')
.filter('filterWithOr', function ($filter) {
var comparator = function (actual, expected) {
if (angular.isUndefined(actual)) {
// No substring matching against `undefined`
return false;
}
if ((actual === null) || (expected === null)) {
// No substring matching against `null`; only match against `null`
return actual === expected;
}
if ((angular.isObject(expected) && !angular.isArray(expected)) || (angular.isObject(actual) && !hasCustomToString(actual))) {
// Should not compare primitives against objects, unless they have custom `toString` method
return false;
}
console.log('ACTUAL EXPECTED')
console.log(actual)
console.log(expected)
actual = angular.lowercase('' + actual);
if (angular.isArray(expected)) {
var match = false;
expected.forEach(function (e) {
console.log('forEach')
console.log(e)
e = angular.lowercase('' + e);
if (actual.indexOf(e) !== -1) {
match = true;
}
});
return match;
} else {
expected = angular.lowercase('' + expected);
return actual.indexOf(expected) !== -1;
}
};
return function (array, expression) {
return $filter('filter')(array, expression, comparator);
};
});
And then we can use it anywhere we want:
$scope.list=[
{name:'Jack Bauer'},
{name:'Chuck Norris'},
{name:'Superman'},
{name:'Batman'},
{name:'Spiderman'},
{name:'Hulk'}
];
<ul>
<li ng-repeat="item in list | filterWithOr:{name:['Jack','Chuck']}">
{{item.name}}
</li>
</ul>
Finally here's a plunkr.
Note: Expected array should only contain simple objects like String, Number etc.
you can use searchField filter of angular.filter
JS:
$scope.users = [
{ first_name: 'Sharon', last_name: 'Melendez' },
{ first_name: 'Edmundo', last_name: 'Hepler' },
{ first_name: 'Marsha', last_name: 'Letourneau' }
];
HTML:
<input ng-model="search" placeholder="search by full name"/>
<th ng-repeat="user in users | searchField: 'first_name': 'last_name' | filter: search">
{{ user.first_name }} {{ user.last_name }}
</th>
<!-- so now you can search by full name -->
You can also use ngIf if the situation permits:
<div ng-repeat="p in [
{ name: 'Justin' },
{ name: 'Jimi' },
{ name: 'Bob' }
]" ng-if="['Jimi', 'Bob'].indexOf(e.name) > -1">
{{ p.name }} is cool
</div>
The quickest solution that I've found is to use the filterBy filter from angular-filter, for example:
<input type="text" placeholder="Search by name or genre" ng-model="ctrl.search"/>
<ul>
<li ng-repeat="movie in ctrl.movies | filterBy: ['name', 'genre']: ctrl.search">
{{movie.name}} ({{movie.genre}}) - {{movie.rating}}
</li>
</ul>
The upside is that angular-filter is a fairly popular library (~2.6k stars on GitHub) which is still actively developed and maintained, so it should be fine to add it to your project as a dependency.
I believe this is what you're looking for:
<div>{{ (collection | fitler1:args) + (collection | filter2:args) }}</div>
Please try this
var m = angular.module('yourModuleName');
m.filter('advancefilter', ['$filter', function($filter){
return function(data, text){
var textArr = text.split(' ');
angular.forEach(textArr, function(test){
if(test){
data = $filter('filter')(data, test);
}
});
return data;
}
}]);
Lets assume you have two array, one for movie and one for genre
Just use the filter as: filter:{genres: genres.type}
Here genres being the array and type has value for genre
I wrote this for strings AND functionality (I know it's not the question but I searched for it and got here), maybe it can be expanded.
String.prototype.contains = function(str) {
return this.indexOf(str) != -1;
};
String.prototype.containsAll = function(strArray) {
for (var i = 0; i < strArray.length; i++) {
if (!this.contains(strArray[i])) {
return false;
}
}
return true;
}
app.filter('filterMultiple', function() {
return function(items, filterDict) {
return items.filter(function(item) {
for (filterKey in filterDict) {
if (filterDict[filterKey] instanceof Array) {
if (!item[filterKey].containsAll(filterDict[filterKey])) {
return false;
}
} else {
if (!item[filterKey].contains(filterDict[filterKey])) {
return false;
}
}
}
return true;
});
};
});
Usage:
<li ng-repeat="x in array | filterMultiple:{key1: value1, key2:[value21, value22]}">{{x.name}}</li>
Angular Or Filter Module
$filter('orFilter')([{..}, {..} ...], {arg1, arg2, ...}, false)
here is the link: https://github.com/webyonet/angular-or-filter
I had similar situation. Writing custom filter worked for me. Hope this helps!
JS:
App.filter('searchMovies', function() {
return function (items, letter) {
var resulsts = [];
var itemMatch = new RegExp(letter, 'i');
for (var i = 0; i < items.length; i++) {
var item = items[i];
if ( itemMatch.test(item.name) || itemMatch.test(item.genre)) {
results.push(item);
}
}
return results;
};
});
HTML:
<div ng-controller="MoviesCtrl">
<ul>
<li ng-repeat="movie in movies | searchMovies:filterByGenres">
{{ movie.name }} {{ movie.genre }}
</li>
</ul>
</div>
Here is my example how create filter and directive for table jsfiddle
directive get list (datas) and create table with filters
<div ng-app="autoDrops" ng-controller="HomeController">
<div class="row">
<div class="col-md-12">
<h1>{{title}}</h1>
<ng-Multiselect array-List="datas"></ng-Multiselect>
</div>
</div>
</div>
my pleasure if i help you
Too late to join the party but may be it can help someone:
We can do it in two step, first filter by first property and then concatenate by second filter:
$scope.filterd = $filter('filter')($scope.empList, { dept: "account" });
$scope.filterd = $scope.filterd.concat($filter('filter')($scope.empList, { dept: "sales" }));
See the working fiddle with multiple property filter
OPTION 1:
Using Angular providered filter comparator parameter
// declaring a comparator method
$scope.filterBy = function(actual, expected) {
return _.contains(expected, actual); // uses underscore library contains method
};
var employees = [{name: 'a'}, {name: 'b'}, {name: 'c'}, {name: 'd'}];
// filter employees with name matching with either 'a' or 'c'
var filteredEmployees = $filter('filter')(employees, {name: ['a','c']}, $scope.filterBy);
OPTION 2:
Using Angular providered filter negation
var employees = [{name: 'a'}, {name: 'b'}, {name: 'c'}, {name: 'd'}];
// filter employees with name matching with either 'a' or 'c'
var filteredEmployees = $filter('filter')($filter('filter')(employees, {name: '!d'}), {name: '!b'});
My solution
ng-repeat="movie in movies | filter: {'Action'} + filter: {'Comedy}"
the best answer is :
filter:({genres: 'Action', genres: 'Comedy'}
I need to show object only if one of it's properties equals to array.
I have a controller in app.js:
app.controller('checkBoxController', function ($scope) {
$scope.ingredients= [
{label: 'Egg', value: 1},
{label: 'Milk', value: 2},
$scope.selection=[];
$scope.toggleSelection = function toggleSelection(ingredientLabel) {
var idx = $scope.selection.indexOf(ingredientLabel);
if (idx > -1) {
$scope.selection.splice(idx, 1);
}
else {
$scope.selection.push(ingredientLabel);
}
};
});
and an html code for it:
<span style="color:black;" class="selected-item">Selected Items:<span>
<div ng-repeat="label in selection" class="selected-item">
</div>
<div class="list-group">
<div class="list-group-item" ng-repeat="product in meals.products" ng-show="product.contents==selection">
<h1>{{product.name}}</h1>
<meal-gallery></meal-gallery>
<meal-tabs></meal-tabs>
</div>
</div>
And I Have { name: 'Scrambled Egg', contents: "Egg"} in array of products. So I need to show product if it's contents equals to selected ingredients.
I do not have problems when it is only one ingredient like "Egg", but if I need contents of two equal to selected?
It would be best to use a custom filter.
If you add lodash to your application, you can create a filter that will preform a following operation:
angular.module('common', [])
.filter('canBeMadeFrom', function() {
return function(product, ingredients) {
return _.intersection(product.contents, ingredients).length == product.contents.length';
};
});
this will return true if all of products contents are contained in ingredients
use it like this
ng-repeat='product in products | canBeMadeFrom:ingredients'
Use a filter.
<div ng-repeat=product in products |filter: product.a === a && product.b === b>
You can also use a function for filter. It's a function that takes in an item and returns Boolean
I Have an array of items like this which contains a list of animals and a list of fruits in a random order.
$scope.items = [{name:'mango',type:'fruit'},{name:'cat',type:'animal'},{name:'dog',type:'animal'},{name:'monkey',type:'animal'},{name:'orange',type:'fruit'},{name:'banana',type:'fruit'},...]
Then I Have a array of colors like
$scope.colorSeries = ['#3366cc', '#dc3912', '#ff9900',...];
$scope.setBGColor = function (index) {
return { background: $scope.colorSeries[index] }
}
I am using the items array to render the fruits only in a div with background color selected from the colorSeries based on the index like colorSeries[0] which will give me #3366cc
<div data-ng-repeat="item in items " ng-if="item.type =='fruit'">
<label ng-style="setBGColor($index)">{{item.name}}</label>
</div>
Things working fine if the length of the items array is less than length of colorSeries array.The problem arises if the length of colorSeries array is less than the items array.e.g if i have a color series with 3 colors then for this items array the last item i.e orange will need a color indexed as colorSeries[4] which is undefined where as I have rendered only three items. So, is it possible to get the index like 0,1,2 i.e the index of elements rendered with ng-if.
Instead of using ng-if, I would use a filter. then, the $index will be always correspond to the index in the result list after applying the filter
<div data-ng-repeat="item in items|filterFruit">
<label ng-style="setBGColor($index)">{{item.name}}</label>
</div>
angular.module('app.filters', []).filter('filterFruit', [function () {
return function (fruits) {
var i;
var tempfruits = [];
var thefruit;
if (angular.isDefined(fruits) &&
fruits.length > 0) {
for(thefruit = fruits[i=0]; i<fruits.length; thefruit=fruits[++i]) {
if(thefruit.type =='fruit')
tempfruits.push(thefruit);
}
}
return tempfruits;
};
}]);
Try this..
this.itemList = [{
name:'apple'
}, {
name: 'fruit'
}];
this.colorlist = [{ color: 'red' }, { color: 'orange' }, { color: 'blue' }];
<div data-ng-repeat="item in itemList " ng-if="item.name=='fruit'">
<label ng-style="colorlist [$index]">{{item.name}}</label>
</div>
If I were you, I would embed the colors inside the $scope.items objects, since you always use them coupled with a fruit.
Anyway, to address your specific code configuration, I would add a counter in my controller and use it to loop through the colors.
Something like this:
app.controller('myCtrl', function ($scope) {
$scope.colorSeries = ['#3366cc', '#dc3912', '#ff9900',...];
var colorCounter = $scope.colorSeries.length;
var colorIdx = -1;
$scope.setBGColor = function () {
// get back to the first color if you finished the loop
colorIdx = colorCounter? 0: colorIdx+1
return { background: $scope.colorSeries[colorIdx] }
}
})
And then in your view (note that there is no $index)
<div data-ng-repeat="item in items " ng-if="item.type =='fruit'">
<label ng-style="setBGColor()">{{item.name}}</label>
</div>
I have a AngularJS directive that allows users to select a values from a list to filter on. Pretty simple concept which is represented here:
Problem is when I click one of the checkboxes they all select unintended. My directive is pretty simple so I'm not sure why this is happening. The code around the selection and checkboxes is as follows:
$scope.tempFilter = {
id: ObjectId(),
fieldId: $scope.available[0].id,
filterType: 'contains'
};
$scope.toggleCheck = function (id) {
var values = $scope.tempFilter.value;
if (!values || !values.length) {
values = $scope.tempFilter.value = [];
}
var idx = values.indexOf(id);
if (idx === -1) {
values.push(id);
} else {
values.splice(idx, 1);
}
};
$scope.valuesListValues = function (id) {
return $scope.available.find(function (f) {
return f.id === id;
}).values;
};
and the data resembles:
$scope.available = [{
id: 23,
name: 'Store'
values: [
{ id: 124, name: "Kansas" },
{ id: 122, name: "Florida" }, ... ]
}, ... ]
the view logic is as follows:
<ul class="list-box">
<li ng-repeat="val in valuesListValues(tempFilter.fieldId)">
<div class="checkbox">
<label ng-click="toggleCheck(val.id)">
<input ng-checked="tempFilter.value.indexOf(val.id) === -1"
type="checkbox"> {{val.name}}
</label>
</div>
</li>
</ul>
First off, it toggleCheck fires twice but populates the correct data ( second time given my code it removes it though ).
After the second fire, it checks all boxes... Any ideas?
Perhaps its that the local variable doesn't get reassigned to the property of the scope property used in the view. Since your values are then non-existent and not found, the box is checked.
$scope.tempFilter.value = values
I took the interface concept you were after and created a simpler solution. It uses a checked property, found in each item of available[0].values, as the checkbox model. At the top of the list is a button that clears the selected items.
JavaScript:
function DataMock($scope) {
$scope.available = [{
id: 23,
name: 'Store',
values: [{
id: 124,
name: "Kansas"
}, {
id: 122,
name: "Florida"
}]
}];
$scope.clearSelection = function() {
var values = $scope.available[0].values;
for (var i = 0; i < values.length; i++) {
values[i].checked = false;
}
};
}
HTML:
<body ng-controller="DataMock">
<ul class="list-box">
<li>
<button ng-click="clearSelection()">Clear Selection</button>
</li>
<li ng-repeat="val in available[0].values">
<div class="checkbox">
<label>
<input ng-model="val.checked"
type="checkbox" /> {{val.name}}
</label>
</div>
</li>
</ul>
</body>
Demo on Plunker
The repeat that I used to grab the values based on the id, was the problem area.
<li ng-repeat="val in valuesListValues(tempFilter.fieldId)">
removing that and simple listening and setting a static variable resolved the problem.
$scope.$watch('tempFilter.fieldId', function () {
var fId = $scope.tempFilter.fieldId;
if ($scope.isFieldType(fId, 'valuesList')) {
$scope.valuesListValues = $scope.valuesListValues(fId);
}
}, true);
});
and then in the view:
ng-repeat="value in valuesListValues"