This is going to be a rather longwinded question, so please bear with me...
I have an array of about 25-30 items. They are sorted through various filters such as brand, type, material, size, etc.. How can I go about building a searchable filter. All of the ones I've seen just include a filter:query | in their filters. However I can't get mine to query my existing array.
Here is what my array looks like, only going to show 1 item to keep size down..
$scope.products = [
{
src: 'images/img/image1.jpg',
name: 'XXX-1A',
brand: 'Brand A',
material: 'dry',
size: '00',
type: 'dry pipe',
color:'red'
}];
Function for filtering (only included 1 to save space):
$scope.brandIncludes = [];
$scope.includeBrand = function(brand) {
var i = $.inArray(brand, $scope.brandIncludes);
if (i > -1) {
$scope.brandIncludes.splice(i, 1);
} else {
$scope.brandIncludes.push(brand);
}
}
$scope.brandFilter = function(products) {
if ($scope.brandIncludes.length > 0) {
if ($.inArray(products.brand, $scope.brandIncludes) < 0)
return;
}
return true;
}
This is what I am using to filter from the HTML, I am using checkboxes to select each filter:
<div class="info" ng-repeat="p in products |
filter:brandFilter |
filter:materialFilter |
filter:typeFilter |
filter:styleFilter">
</div>
My search bar mark up:
<div class="filtering">
<div class="search-sect">
<input name="dbQuery" type="text" placeholder="Search pieces" class="search-input" ng-model="query"/>
</div>
One of the filter's mark up:
<input type="checkbox" ng-click="includeStyle('adaptor')"/>Adaptor<br>
Now that you have all the code, here are some of the things I've tried that don't seem to be running right:
My Attempt:
Search bar:
<input type="text" id="query" ng-model="query"/>
Filter:
<li ng-repeat="p in products | filter:query | orderBy: orderList">
I understand that to some experienced with angular, this is a relatively easy task, but I am just learning and can't seem to wrap my head around searching a query. It's probably a simple solution that I am overlooking. This is my first Angular app and I am trying to bite off more than I can chew in order to learn more.
I appreciate all responses, thanks in advance!
As per request: CodePen
The simple built-in angular filter is not smart enough to to work with your checkbox design, so try writing a custom filter. You will need to bind the checkboxes you mentioned to variables in your scope, e.g. brandFilterIsEnabled. See the tutorial for writing custom filters. Here is a working example.
var myApp = angular.module('myApp', []);
myApp.controller('ctrl', function ($scope) {
$scope.items = [{
name:'foo',
color:'red'
},{
name:'bar',
color:'blue'
},{
name:'baz',
color:'green'
}];
$scope.searchNames = true;
$scope.searchColors = true;
$scope.$watch('searchColors', function(){
$scope.searchKeys = [ $scope.searchNames ? 'name' : null, $scope.searchColors ? 'color' : null ];
});
$scope.$watch('searchNames', function(){
$scope.searchKeys = [ $scope.searchNames ? 'name' : null, $scope.searchColors ? 'color' : null ];
});
});
myApp.filter('advancedSearch', function($filter) {
return function(data, keys, query) {
results = [];
if( !query ){
return data;
} else {
angular.forEach( data, function( obj ){
var matched = false;
angular.forEach( keys, function( key ){
if( obj[key] ){
// match values using angular's built-in filter
if ($filter('filter')([obj[key]], query).length > 0){
// don't add objects to results twice if multiple
// keys have values that match query
if( !matched ) {
results.push(obj);
}
matched = true;
}
}
});
});
}
return results;
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<html ng-app="myApp">
<div ng-controller="ctrl">
<input type='checkbox' ng-model='searchNames'>search names</input>
<input type='checkbox' ng-model='searchColors'>search colors</input>
<input type='text' ng-model='query'>search objects</input>
<ul>
<li ng-repeat="item in items | advancedSearch : searchKeys : query">
<span style="color:{{item.color}}">{{item.name}}</span>
</li>
</ul>
</div>
</html>
Related
I'm trying to add filter for searching the product by from price to price. I have following array in json format as $scope.products in the controller.
[
{"product_id":"1","product_price":"185"},
{"product_id":"2","product_price":"45"},
{"product_id":"3","product_price":"123"},
{"product_id":"4","product_price":"23"},
{"product_id":"5","product_price":"776"}
]
And below is the loop in html
<ul class="items">
<li ng-repeat="product in products | filter:{product_name: findname} | sizeFilter:(size_arr|filter:{on:true})" >
<strong>{{product.product_name | CapsFirst}}</strong>
<strong>({{product.product_category}})</strong>
<strong>({{product.product_size}})</strong>
<strong>({{product.product_color}})</strong>
</li>
</ul>
In the view I want to add two input fields for searching the products by price.
<input type="number" ng-model="from_price" />
<input type="number" ng-model="to_price" />
Can anyone guide me that I can develop in proper way. I've searched about it but couldn't found a tutorial, I'm not expertise in angularjs. Thank You...
Here's an example on plunkr of what you've been asking for. I have added a product name in order to distinguish them, try it.
https://plnkr.co/edit/NKos4x9eeB5dZQaypurg?p=preview
app.filter('sizeFilter', [function() {
return function(values, config) {
if (config.enabled) {
var filterResult = [];
angular.forEach(values, function(value) {
if (value.product_price >= config.from_price && value.product_price <= config.to_price) {
filterResult.push(value);
};
});
return filterResult;
}
return values;
}
}]);
In HTML
<li ng-repeat="product in products |rangePrice:
{'toPrice':to_price,'fromPrice':from_price}">
In JavaScript
app.filter('rangePrice', function() {
return function( items, rangePrice ) {
var filteredValue= [];
var toPrice= parseInt(rangePrice.toPrice);
var fromPrice= parseInt(rangePrice.fromPrice);
angular.forEach(items, function(item) {
if( item.product_price>= toPrice&& item.product_price<= toPrice) {
filteredValue.push(item);
}
});
return filteredValue;
};
});
I want want to display and count only batter in batters at index 0.
i.e id = 1001 type = regular
and count should be "4"
Maybe this updated JSFiddle will give you an idea.
HTML
<div ng-app="myApp" ng-controller="customersCtrl">
<ul ng-repeat="x in myData">
<li ng-repeat = "batter in x.batters.batter | filter: {'id': 1001, 'type': 'Regular'}">
{{ batter.id }} - {{ batter.type }}
</li>
</ul>
{{countBatters(1001, 'Regular')}}
</div>
Added function in controller:
$scope.countBatters = function(id, type) {
return $scope.myData.filter(function(obj) {
var batter = obj.batters.batter[0];
return batter.id == id && batter.type == type
}).length;
}
Use array filter method..
var getBatter1001 = (item) => { return item.batters.batter.filter((x) => {x.id === '1001'}); }
var batter1001 = $scope.myData.filter(getBatter1001);
console.log('Count of objects with batter id 1001: '+batter1001.length); // Logs 3
I'm attempting to make a dynamic form in Angular 1.4.7 in which:
There are multiple reports (vm.reports = [];)
Each report can be assigned ONE report object via vm.reportOptions.
Each vm.reportOptions can only be selected ONCE across multiple reports, which is filtered via exclude.
Each report supports MANY dimension objects via vm.dimensionOptions.
Each dimension can only be selected ONCE per report, which is filtered via excludeDimensions (subsequent reports have access to all the dimensionOptions and filter on their own).
These requirements are all working (roughly) with the exception of:
If I add two reports, and add the exact same dimensions (ie: Report One > Dimension One > Enable Dimension Filter and Report Two > Dimension One > Enable Dimension Filter) for each of the reports, changing the select inside of Enable Dimensions Filter changes it in both the reports.
I assume that this is happening due to the fact that I'm pushing the actual dimension objects in to each reports dimensions: [] array and that they are still pointing to the same object.
-- EDITS --
I realize angular.clone() is a good way to break this reference, but the <select> code I wrote is automatically piping in the object to the model. I was tempted to give each report their own controller and giving each report their own copy() of the options.
Would this work? Or is there a better way?
I have a working JSBin here.
Pertinent Code:
HTML:
<body ng-app="app">
<div ng-controller="AlertsController as alerts">
<pre>{{alerts.output(alerts.reports)}}</pre>
<div class="container">
<div
ng-repeat="report in alerts.reports"
class="report"
>
<button
ng-if="$index !== 0"
ng-click="alerts.removeItem(alerts.reports,report)"
>Delete Report</button>
<label>Select Report</label>
<select
ng-model="alerts.reports[$index].report"
ng-init="report"
ng-options="reportSelect.niceName for reportSelect in alerts.reportOptions | exclude:'report':alerts.reports:report"
></select>
<div
ng-repeat="dimension in report.dimensions"
class="condition"
>
<div class="select">
<h1 ng-if="$index === 0">IF</h1>
<h1 ng-if="$index !== 0">AND</h1>
<select
ng-model="report.dimensions[$index]"
ng-change="alerts.checkThing(report.dimensions,dimension)"
ng-init="dimension"
ng-options="dimensionOption.niceName for dimensionOption in alerts.dimensionOptions | excludeDimensions:report.dimensions:dimension"
>
<option value="123">Select Option</option>
</select>
<button
class="delete"
ng-if="$index !== 0"
ng-click="alerts.removeItem(report.dimensions,dimension)"
>Delete</button>
</div>
<input type="checkbox" ng-model="dimension.filtered" id="filter-{{$index}}">
<label class="filter-label" for="filter-{{$index}}">Enable Dimension Filter</label>
<div ng-if="dimension.filtered">
<select
ng-model="dimension.operator"
ng-options="operator for operator in alerts.operatorOptions">
</select>
<input
ng-model="dimension.filterValue"
placeholder="Text"
></input>
</div>
</div>
<button
ng-click="alerts.addDimension(report)"
ng-if="report.dimensions.length < alerts.dimensionOptions.length"
>Add dimension</button>
</div>
<button
ng-if="alerts.reports.length < alerts.reportOptions.length"
ng-click="alerts.addReport()"
>Add report</button>
<!--
<div ng-repeat="sel in alerts.select">
<select ng-model="alerts.select[$index]" ng-init="sel"
ng-options="thing.name for thing in alerts.things | exclude:alerts.select:sel"></select>
</div>
-->
</div><!-- container -->
</div>
</body>
JS:
var app = angular.module('app', []);
app.controller('AlertsController', function(){
var vm = this;
vm.reportOptions = [
{id: 1, niceName: 'Report One'},
{id: 2, niceName: 'Report Two'},
{id: 3, niceName: 'Report Three'},
];
vm.dimensionOptions = [
{id: 1, niceName: 'Dimension One'},
{id: 2, niceName: 'Dimension Two'},
{id: 3, niceName: 'Dimension Three'},
];
vm.operatorOptions = [
'>',
'>=',
'<',
'<=',
'=',
'!='
];
////// DEBUG STUFF //////
vm.output = function(value) {
return JSON.stringify(value, undefined, 4);
}
////////////////////////
vm.reports = [];
vm.addReport = function() {
vm.reports.push({report: {id: null}, dimensions: []});
}
vm.removeItem = function(array,item) {
if(array && item) {
var index = array.indexOf(item);
if(index > -1) {
array.splice(index,1);
}
}
}
vm.addDimension = function(report) {
console.log('addDimension',report);
if(report) {
report.dimensions.push({})
}
};
// init
if(vm.reports.length === 0) {
vm.reports.push({report: {}, dimensions: [{}]});
// vm.reports.push({report: vm.reportOptions[0], dimensions: [vm.dimensionOptions[0]]});
}
});
app.filter('excludeDimensions', [function() {
return function(input,select,selection) {
// console.log('ed',input,select,selection);
var newInput = [];
for(var i = 0; i < input.length; i++){
var addToArray=true;
for(var j=0;j<select.length;j++){
if(select[j].id===input[i].id){
addToArray=false;
}
}
if(addToArray || input[i].id === selection.id){
newInput.push(input[i]);
}
}
return newInput;
}
}]);
app.filter('exclude', [function () {
return function(input,type,select,selection){
var newInput = [];
for(var i = 0; i < input.length; i++){
var addToArray=true;
for(var j=0;j<select.length;j++){
if(select[j][type].id===input[i].id){
addToArray=false;
}
}
if(addToArray || input[i].id === selection[type].id){
newInput.push(input[i]);
}
}
return newInput;
};
}]);
How do I get around pushing same object reference to array
Use angular.copy()
array.push(angular.copy(vm.formObject));
// clear object to use again in form
vm.formObject={};
I ended up using select as so that it just set an id on the object instead of pointing to the original object. This solved the problem.
Okay so i have the following small translation file:
{
"components" : {
"1" : "Video",
"2" : "Lyd",
"3" : "Dokument",
"4" : "Tekst"
}
}
And then i have the following li item:
<li ng-repeat="type in componentTypes" ng-hide="module.module_type_id == 2 || module.module_type_id == 10">{{type.name}}</li>
What you need to notice is :
{{type.name}}
Or more precisely:
translate="components.{{1}}"
With this it does not translate the <a></a> tag.
However if i do
translate="components.1"
it translates correctly however this method doesnt work for me
so my question is how can dynamicly change the value of a the translate attribute?
The reason components.{{1}} is not working is because the double curlies in Angular is just meant to evaluate an expression. 1 is just a number, so you'll get components.1 everytime.
If I understand what you need correctly, you need to have a corresponding component based on type. So if type.id === 1 then your type is Video.
In order to achieve that in Angular dynamically, you should just have:
translate="{{components[type.id]}}"
Fiddle
As far as I understand you want to dynamize the translation json if added a new type to your componentTypes array.
There is a solution for that need, you can implement a new custom translation loader factory and use it with specified way here https://github.com/angular-translate/angular-translate/wiki/Asynchronous-loading. After that you have to add this new item to the translation json, your array and then refresh the translation.
View:
<div ng-app="myApp">
Links to jsfiddle.net must be accompanied by code. Please indent all code by 4 <div ng-controller="MyCtrl">
<input type="text" ng-model="type.name" /> Add Component
<ul>
<li ng-repeat="type in componentTypes" ng-hide="module.module_type_id == 2 || module.module_type_id == 10">{{ 'components.' + type.name | translate }}</li>
</ul>
</div>
</div>
Implementation of your application:
var myApp = angular.module('myApp', ['pascalprecht.translate']);
var components_en = {
"components": {
"1": "Video",
"2": "Lyd",
"3": "Dokument",
"4": "Tekst"
}
};
myApp.config(function ($translateProvider) {
$translateProvider.useLoader('customLoader', {});
$translateProvider.translations('en', components_en);
$translateProvider.preferredLanguage('en');
});
myApp.controller('MyCtrl', function ($scope, $translate) {
$scope.module = {
module_type_id: 1
};
$scope.addComponent = function (type) {
// Add the componentTypes array you took from database
$scope.componentTypes.push({
name: $scope.componentTypes.length + 1
});
// Add the translation object
components_en["components"][$scope.componentTypes.length] = type.name;
console.log(components_en);
$translate.refresh();
};
$scope.componentTypes = [{
name: 1
}, {
name: 2
}, {
name: 3
}, {
name: 4
}];
});
myApp.factory('customLoader', function ($http, $q) {
return function (options) {
var deferred = $q.defer();
deferred.resolve(components_en);
return deferred.promise;
}
})
I prepared a demonstration for that like usage, please below jsfiddle:
http://jsfiddle.net/nerezo/1z071wzg/6/
Note: This like translation modifications will not be persistent and new translations will be lost.
Please give this a try:
translate="{{'components.' + type.id}}" //if there is id in type
or
translate="{{'components.' + ($index + 1)}}"
Is it possible to filter an array of objects, such that the value of property can be either of a few values (OR condition) without writing a custom filter
This is similar to this problem - Angular.js ng-repeat :filter by single field
But instead of
<div ng-repeat="product in products | filter: { color: 'red' }">
is it possible to do something like this
<div ng-repeat="product in products | filter: { color: 'red'||'blue' }">
for a sample data as follows-
$scope.products = [
{ id: 1, name: 'test', color: 'red' },
{ id: 2, name: 'bob', color: 'blue' }
/*... etc... */
];
I've unsuccessfully tried
<div ng-repeat="product in products | filter: { color: ('red'||'blue') }">
Best way to do this is to use a function:
<div ng-repeat="product in products | filter: myFilter">
$scope.myFilter = function (item) {
return item === 'red' || item === 'blue';
};
Alternatively, you can use ngHide or ngShow to dynamically show and hide elements based on a certain criteria.
For me, it worked as given below:
<div ng-repeat="product in products | filter: { color: 'red'||'blue' }">
<div ng-repeat="product in products | filter: { color: 'red'} | filter: { color:'blue' }">
I thing ng-if should work:
<div ng-repeat="product in products" ng-if="product.color === 'red'
|| product.color === 'blue'">
In HTML:
<div ng-repeat="product in products | filter: colorFilter">
In Angular:
$scope.colorFilter = function (item) {
if (item.color === 'red' || item.color === 'blue') {
return item;
}
};
Here is a way to do it while passing in an extra argument:
https://stackoverflow.com/a/17813797/4533488 (thanks to Denis Pshenov)
<div ng-repeat="group in groups">
<li ng-repeat="friend in friends | filter:weDontLike(group.enemy.name)">
<span>{{friend.name}}</span>
<li>
</div>
With the backend:
$scope.weDontLike = function(name) {
return function(friend) {
return friend.name != name;
}
}
.
And yet another way with an in-template filter only:
https://stackoverflow.com/a/12528093/4533488 (thanks to mikel)
<div ng:app>
<div ng-controller="HelloCntl">
<ul>
<li ng-repeat="friend in friends | filter:{name:'!Adam'}">
<span>{{friend.name}}</span>
<span>{{friend.phone}}</span>
</li>
</ul>
</div>
I found a more generic solution with the most angular-native solution I can think. Basically you can pass your own comparator to the default filterFilter function. Here's plunker as well.
After not able to find a good universal solution I made something of my own. I have not tested it for a very large list.
It takes care of nested keys,arrays or just about anything.
Here is the github and demo
app.filter('xf', function() {
function keyfind(f, obj) {
if (obj === undefined)
return -1;
else {
var sf = f.split(".");
if (sf.length <= 1) {
return obj[sf[0]];
} else {
var newobj = obj[sf[0]];
sf.splice(0, 1);
return keyfind(sf.join("."), newobj)
}
}
}
return function(input, clause, fields) {
var out = [];
if (clause && clause.query && clause.query.length > 0) {
clause.query = String(clause.query).toLowerCase();
angular.forEach(input, function(cp) {
for (var i = 0; i < fields.length; i++) {
var haystack = String(keyfind(fields[i], cp)).toLowerCase();
if (haystack.indexOf(clause.query) > -1) {
out.push(cp);
break;
}
}
})
} else {
angular.forEach(input, function(cp) {
out.push(cp);
})
}
return out;
}
})
HTML
<input ng-model="search.query" type="text" placeholder="search by any property">
<div ng-repeat="product in products | xf:search:['color','name']">
...
</div>