Searching through multiple ng-repeats at once - javascript

i'm currently working on an application that is build with AngularJS as a base, and that obtains data through the prestashop webservice. All data obtained are JSON strings sorted through multiple files. Now i'm trying to create a searchbox that filters through some objects the moment the user fills in the searchbox. The easy way is ofcourse by using the ng-model and filter: combination like below:
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.names = [
'Jani',
'Carl',
'Margareth',
'Hege',
'Joe',
'Gustav',
'Birgit',
'Mary',
'Kai'
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE html>
<html>
<body>
<div ng-app="myApp" ng-controller="namesCtrl">
<p>Type a letter in the input field:</p>
<p><input type="text" ng-model="test"></p>
<ul>
<li ng-repeat="x in names | filter:test">
{{ x }}
</li>
</ul>
</div>
<p>The list will only consists of names matching the filter.</p>
</body>
</html>
But what if you're using two different sources? and two different ng-repeats?
So in my application some of the data is about customers. The data is obtained through two different $http.get() functions. One is for the customers basic information. The second one is the address information. Take a look below:
// Get the customers
$http.get('config/get/getCustomers.php', {cache: true}).then(function(response){
$scope.customers = response.data.customers.customer
});
// Get the addresses
$http.get('config/get/getAddress.php', {cache: true}).then(function (response) {
$scope.addresses = response.data.addresses.address
});
By using ng-repeat and ng-if i'm able to filter the information and connect it together. ng-if="customer.id == address.id_customer" ng-repeat=...
A full example below:
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.customers = [{
'id': 1,
'name': 'Jani'
},{
'id': 2,
'name': 'Carl'
},
{
'id': 3,
'name': 'Tim'
},
{
'id': 4,
'name': 'Tom'
}
];
$scope.addresses = [{
'id': 1,
'id_customer': 1,
'place': 'Street 12'
},{
'id': 2,
'id_customer': 2,
'place': 'Other street'
},
{
'id': 3,
'id_customer': 3,
'place': 'marioworld!'
},
{
'id': 4,
'id_customer': 4,
'place': 'Space!'
}
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="namesCtrl">
<div ng-repeat="customer in customers">
<div ng-bind="customer.id + ' - ' + customer.name"></div>
<div ng-if="customer.id == address.id_customer" ng-repeat="address in addresses" ng-bind="address.place">
</div>
</div>
</div>
So as you can see i'm able to create the combination with the ng-if but now i would like to create a search input that's able to search through both fields. And that's where my issue starts. I'm able to create it for one ng-repeat. But what if i want to Search on the address and the customer? I would like to create the possibility of letting the user search by customer name, street address and ZIP code. But the ZIP code and address are from a different source.
I hope that someone has found a solution for this and if you have any questions please ask them in the comments.
As always, thanks in advance!

I'd suggest to map your customers array adding to each object it's own place this way:
$scope.customers.map( function addPlace(item) {
item.place = $scope.addresses.reduce(function(a,b){
return item.id === b.id_customer ? b.place : a;
}, '');
return item;
})
This way your template will be easier to read, and you will be able to use your previous search.
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.customers = [{
'id': 1,
'name': 'Jani'
},{
'id': 2,
'name': 'Carl'
},
{
'id': 3,
'name': 'Tim'
},
{
'id': 4,
'name': 'Tom'
}
];
$scope.addresses = [{
'id': 1,
'id_customer': 1,
'place': 'Street 12'
},{
'id': 2,
'id_customer': 2,
'place': 'Other street'
},
{
'id': 3,
'id_customer': 3,
'place': 'marioworld!'
},
{
'id': 4,
'id_customer': 4,
'place': 'Space!'
}
];
$scope.customers.map( function addPlace(item) {
item.place = $scope.addresses.reduce(function(a,b){
return item.id === b.id_customer ? b.place : a;
}, '');
return item;
})
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="namesCtrl">
<p><input type="text" ng-model="test"></p>
<div ng-repeat="customer in customers | filter:test">
{{ customer.id }} - {{ customer.name }}
<br>
{{ customer.place}}
</div>
</div>
</div>

Related

Count items in array by using ng-repeat AngularJS

I have a JSONfile like this:
vm.names = [{
'name': 'A'
}, {
'name': 'A'
}, {
'name': 'B'
}, {
'name': 'C'
}, {
'name': 'C'
}]
I using AngularJS filter repeat it in HTML:
<ul ng-repeat ="name in vm.names | unique'name'>
<li>{{name.name}}</li>
</ul>
Here is issue
A
B
C
So I want count item, I want display like this
A num:2
B num:1
C num:2
How can I do that?
Just use the groupBy filter provided by angular-filter like in this runnable fiddle demo:
View
<div ng-controller="MyCtrl">
<ul ng-repeat="item in names | groupBy: 'name' ">
<li>{{item[0].name}}: {{ item.length}}</li>
</ul>
</div>
AngularJS application
var myApp = angular.module('myApp', ['angular.filter']);
myApp.controller('MyCtrl', function($scope) {
$scope.names = [{
'name': 'A'
}, {
'name': 'A'
}, {
'name': 'B'
}, {
'name': 'C'
}, {
'name': 'C'
}];
});

AngularJS: Using ng-selected to select multiple options, based on collection

I have two arrays of objects, one array being a subset of the other:
$scope.taskGroups = [
{id: 1, name: 'group1', description: 'description1'},
{id: 2, name: 'group2', description: 'description2'},
{id: 3, name: 'group3', description: 'description3'}
];
$scope.selectedGroups = [
{id: 1, name: 'group1', description: 'description1'},
{id: 2, name: 'group3', description: 'description3'}
];
After unsuccessfully trying to get my head around using ng-option, I thought that I could perhaps create a function to determine if an option should be selected in the select list, based on what I picked up in the documentation:
ngSelected
- directive in module ng Sets the selected attribute on the element, if the expression inside ngSelected is truthy.
So, I came up with this function:
$scope.inSelectedGroups = function(taskGroup) {
angular.forEach($scope.selectedGroups, function(group) {
if (taskGroup.id == group.id) {
return true;
}
return false;
});
};
and tried to use it in this html:
<select multiple ng-model="selectedGroups" style="width: 100%" size="7">
<option ng-repeat="taskGroup in taskGroups" value="{{taskGroup.id}}" ng-selected="inSelectedGroups(taskGroup)">{{taskGroup.name}}</option>
</select>
but, no dice - the full list of taskGroups shows, but the selectedTaskGroups aren't, well, selected...
Am I barking up the wrong tree here?
the full list of taskGroups shows, but the selectedTaskGroups aren't,
well, selected.
I tried your solution which is using the ngSelected attribute but I was unsuccessful as well so I tried using the ngOptions instead and it works.
angular.module('app', []).controller('TestController', ['$scope',
function($scope) {
$scope.taskGroups = [{
id: 1,
name: 'group1',
description: 'description1'
}, {
id: 2,
name: 'group2',
description: 'description2'
}, {
id: 3,
name: 'group3',
description: 'description3'
}];
$scope.selectedGroups = [{
id: 1,
name: 'group1',
description: 'description1'
}, {
id: 2,
name: 'group3',
description: 'description3'
}];
}
])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="TestController">
<select multiple="true" ng-model="selectedGroups" style="width: 100%" ng-options="taskGroup.id as taskGroup.description for taskGroup in taskGroups track by taskGroup.id" size="7">
</select>
</div>
See carefully, you are returning Boolean value from function defined in angular.forEach parameter and so nothing is returned from inSelectedGroups function
Try modifying your function to:
$scope.inSelectedGroups = function(taskGroup) {
var flag = false;
angular.forEach($scope.selectedGroups, function(group) {
if (taskGroup.id == group.id) {
flag = true;
return;
}
flag = false;
return;
});
return flag;
};

Angular JS - Creating a filter to compare 2 arrays

I am developing a search page for an application. I have searched a lot at google, but I can't find anything than fragmented information about filters.
What I need is a list of checkboxes containing certain lists of elements (like in the example, languages). I had put a filter for ordering, for the name of the client, but I want to add filters to auxiliar tables like (as I said) languages.
Clients:
$scope.clientes = [
{ 'id': '3', 'name': 'Susana', 'surname': 'Rodríguez Torrón', 'languages': [{'id': 1, 'name': 'english'}, {'id': 2, 'name': 'spanish'}] },
{ 'id': '4', 'name': 'Pablo', 'surname': 'Campos Pérez', 'languages': [{'id': 3, 'name': 'german'}, {'id': 5, 'name': 'japanese'}] }
];
Languages:
$langs = [
{'id': 1, 'name': 'english' },
{'id': 2, 'name': 'spanish' },
{'id': 3, 'name': 'german' },
{'id': 4, 'name': 'japanese' }
];
HTML (for checkboxes list):
<div class="row">
<div class="col-md-12">
<h4>Languages</h4>
<div class="checkbox-block" ng-repeat="lang in langs">
<label class="checkbox" for="{{lang.id}}">
<input type="checkbox" ng-model="langs[lang.id]" name="languages_group" id="{{lang.id}}" />
<span ng-bind-html="lang.name"></span>
</label>
</div>
</div>
</div>;
Clients:
<div class="client-wrapper" ng-repeat="client in clients | orderBy:order_field:order_type | filter:query">
... all the data of every client showed here ...
</div>;
In the array $scope.langs I havethe checks checked, now I want to compare it with every client and to show only the clients having that language(s). Can it be done? Can you help me to know how??
EDIT: Code of the filter.
app.filter('langsFilter', function () {
return function (input, $scope) {
var output = [];
to_langs = $scope.filters.langs;
var trueContro = false;
for (var lang in to_langs) {
if (to_langs[lang] === true) {
trueContro = true;
}
}
for (var client in input) {
for (var i = 0; i < input[client].langs.length; i++) {
if (trueContro === true) {
if (to_langs[client.langs[i]]) {
output.push(input[client]);
}
} else {
output.push(input[client]);
}
}
}
return output;
};
});
As you said I posted the code I am working at. It doesn't works right now, I know, but It will give you an idea of what I need to achieve:
We have a set of clients, in addition to another filters, this filter will compare the langs of every client for show only the client with those langs.
(Caveat: for simplicity's sake I've stuffed everything into a directive here; in real life much of this code would belong in a controller or elsewhere)
DON'T DO THIS
This sample shows a filter working as you describe. I had to change your ng-model -- putting ng-model="langs[lang.id]" on a checkbox overwrites the data you had in the langs array with the box's "checked" status, so instead I tied the user's language selections to scope.selectedLangs -- but it otherwise uses your existing data structure.
var app = angular.module("app", []);
app.directive('sampleDirective', function() {
return {
restrict: 'A',
link: function(scope) {
scope.langs = [
{'id': 1, 'name': 'english'},
{'id': 2, 'name': 'spanish'},
{'id': 3, 'name': 'german'},
{'id': 4, 'name': 'japanese'}
];
scope.selectedLangs = {};
scope.clientes = [{
'id': '3',
'name': 'Susana',
'surname': 'Rodríguez Torrón',
'languages': [
{'id': 1, 'name': 'english'},
{'id': 2, 'name': 'spanish'}
]
}, {
'id': '4',
'name': 'Pablo',
'surname': 'Campos Pérez',
'languages': [
{'id': 3, 'name': 'german'},
{'id': 4, 'name': 'japanese'}
]
}];
}
};
});
app.filter('sampleFilter', function() {
return function(clientes, selectedLangs) {
var ret = [];
angular.forEach(clientes, function(client) {
var match = false;
angular.forEach(client.languages, function(l) {
if (selectedLangs[l.id]) {
match = true;
}
});
if (match) {
ret.push(client);
}
});
return ret;
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div sample-directive>
Languages:
<div ng-repeat="lang in langs">
<label>
<input type="checkbox" ng-model="selectedLangs[lang.id]" name="languages_group">{{lang.name}}
</label>
</div>
<br>Clients:
<div ng-repeat="client in clientes | sampleFilter:selectedLangs">{{client.name}} {{client.surname}}
</div>
</div>
</div>
But please consider not doing it this way -- angular filters are not very performant by nature (and this is exacerbated by the inefficient data structure you've chosen for both languages and clients. You should at the very least think about changing your arrays-of-objects-with-IDs into hash tables keyed by those IDs, i.e:
scope.langs = {
1: {name: 'english'},
2: {name: 'spanish'}
// ...etc
}
This would save you having to iterate through so many nested loops to check client languages against available languages -- the way you have it now is the worst of both worlds, because if you need to find anything by ID you still have to iterate through the array.)
DO THIS INSTEAD
Instead of depending on a filter which will run every $digest, you'll be better off watching for changes to the selected languages and updating your results only when needed, like so:
var app = angular.module("app", []);
app.directive('sampleDirective', function() {
return {
restrict: 'A',
link: function(scope) {
scope.langs = [
{'id': 1, 'name': 'english'},
{'id': 2, 'name': 'spanish'},
{'id': 3, 'name': 'german'},
{'id': 4, 'name': 'japanese'}
];
scope.clientes = [{
'id': '3',
'name': 'Susana',
'surname': 'Rodríguez Torrón',
'languages': [
{'id': 1, 'name': 'english'},
{'id': 2, 'name': 'spanish'}
]
}, {
'id': '4',
'name': 'Pablo',
'surname': 'Campos Pérez',
'languages': [
{'id': 3, 'name': 'german'},
{'id': 4, 'name': 'japanese'}
]
}];
scope.selectedLangs = {};
scope.filterByLanguage = function() {
scope.matchedClients = [];
angular.forEach(scope.clientes, function(client) {
var match = false;
angular.forEach(client.languages, function(l) {
if (scope.selectedLangs[l.id]) {
match = true;
}
});
client.matchesLanguage = match;
});
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div sample-directive>
Languages:
<div ng-repeat="lang in langs">
<label>
<input type="checkbox" ng-model="selectedLangs[lang.id]" name="languages_group" ng-change="filterByLanguage()">{{lang.name}}
</label>
</div>
<br>Clients:
<div ng-repeat="client in clientes" ng-show="client.matchesLanguage">{{client.name}} {{client.surname}}
</div>
</div>
</div>
Note the "filter" (now a function on the directive scope) is now only run when the ng-change handler is fired on a language selection; also instead of a separate selectedLanguages variable this just adds a 'matchesLanguage' field to each client for ng-show to use.
WHEN NOTHING IS SELECTED
For the exception requested in comments -- show all the clients if none of the languages are selected -- you could add another loop:
scope.noLanguagesSelected = true;
angular.forEach(Object.keys(scope.selectedLangs), function(k) {
if (scope.selectedLangs[k]) {
scope.noLanguagesSelected = false;
}
});
and then alter your ng-show to show the client if either that specific language, or no language at all is selected (this is probably better than just artificially setting client.matchesLanguage on everything in that case:)
ng-show="client.matchesLanguage || noLanguagesSelected"
as shown in the following:
var app = angular.module("app", []);
app.directive('sampleDirective', function() {
return {
restrict: 'A',
link: function(scope) {
scope.langs = [
{'id': 1, 'name': 'english'},
{'id': 2, 'name': 'spanish'},
{'id': 3, 'name': 'german'},
{'id': 4, 'name': 'japanese'}
];
scope.clientes = [{
'id': '3',
'name': 'Susana',
'surname': 'Rodríguez Torrón',
'languages': [
{'id': 1, 'name': 'english'},
{'id': 2, 'name': 'spanish'}
]
}, {
'id': '4',
'name': 'Pablo',
'surname': 'Campos Pérez',
'languages': [
{'id': 3, 'name': 'german'},
{'id': 4, 'name': 'japanese'}
]
}];
scope.selectedLangs = {};
scope.noLanguagesSelected = true;
scope.filterByLanguage = function() {
angular.forEach(scope.clientes, function(client) {
var match = false;
angular.forEach(client.languages, function(l) {
if (scope.selectedLangs[l.id]) {
match = true;
}
});
client.matchesLanguage = match;
});
/*
special case: if no checkboxes checked, pretend they all match.
(In real life you'd probably wnat to represent this differently
in the UI rather than just setting matchesLanguage=true).
We can't just check to see if selectedLangs == {}, because if user
checked and then unchecked a box, selectedLangs will contain {id: false}.
So we have to actually check each key for truthiness:
*/
scope.noLanguagesSelected = true; // assume true until proved otherwise:
angular.forEach(Object.keys(scope.selectedLangs), function(k) {
if (scope.selectedLangs[k]) {
scope.noLanguagesSelected = false; // proved otherwise.
}
});
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div sample-directive>
Languages:
<div ng-repeat="lang in langs">
<label>
<input type="checkbox" ng-model="selectedLangs[lang.id]" name="languages_group" ng-change="filterByLanguage()">{{lang.name}}
</label>
</div>
<br>Clients:
<div ng-repeat="client in clientes" ng-show="client.matchesLanguage || noLanguagesSelected">{{client.name}} {{client.surname}}
</div>
</div>
</div>
I would show all clients with css's "display:none" and a additional class with the language, so, then you can suscribe to the filter event:
filterEl.addEventListener("onchange",filter);
function filter(ev){
// first hide all clients
document.getElementsByClassName("clients").forEach(function(){
this.style.display = "none";
});
// then show all clients with XXX language
var checkbox = ev.target;
document.getElementsByClassName(checkbox.id).forEach(function(){
this.style.display = "block";
});
}
CLIENT
<div class="client-wrapper LANGUAGE" ng-repeat="client in clients | orderBy:order_field:order_type | filter:query">
... all the data of every client showed here ...
</div>

Group data within a one-dimension list using Angular

I use Angular and Bootstrap to represent a data grid and give user some control over it (edit data, etc.). The data set is array of objects, each object has a group property, which is not unique and represents what group a record refers to.
So, the dataset looks like
[
{
id: 1,
group: 'A',
value: 'John'
}, {
id: 2,
group: 'A',
value: 'Jake'
}, {
id: 3,
group: 'B',
value: 'Jack'
}
]
I want Angular to output
<div class="row group">
<div class="col-md-12">A</div>
</div>
<div class="row sample">
<div class="col-md-4">1</div>
<div class="col-md-8">John
<div>
...
I tried ng-repeat but it only allows to fold arrays one into another, so the {{ group }} will be a top element and {{ elementOfAGroup }} will be its child. I need a final markup to be a plain set of DOM elements.
I googled for a solution but the only I've found were simple components (directives) that allow making up tables.
I have used custom "uniq" filter to accomplish this:
<!doctype html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-filter/0.1.1/angular-filter.js"></script>
</head>
<body ng-app="plunker" ng-controller="MainCtrl">
<div class="row group" ng-repeat="data in dataset | uniq: 'group'">
<h3>{{data.group}}</h3>
<div class="col-md-12" ng-repeat="child in dataset | filter: { group: data.group }">{{child.value}}</div>
</div>
</body>
<script>
var app = angular.module('plunker', ['angular.filter']);
app.controller('MainCtrl', ['$scope', '$sce', function($scope, $sce) {
$scope.dataset = [
{
id: 1,
group: 'A',
value: 'John'
}, {
id: 2,
group: 'A',
value: 'Jake'
}, {
id: 3,
group: 'B',
value: 'Jack'
}
];
}]);
</script>
</html>
It may be slow with large data set.

orderBy descending order doesn't work in my case?

I expect to see decesecing order base on points work but it doesn't, here is the demo http://jsfiddle.net/uRPSL/34/
ng-repeat
<ul ng-repeat="friend in user">
<li ng-repeat="relation in friend.relationship | orderBy:'points':true">
{{relation.name}}
</li>
</ul>
js
app.controller('Controller', function ($scope, $rootScope, $location) {
$scope.user = [{
'uId': 1,
'name': 'Joe',
'relationship': [{
'uId': 2,
'name': 'Jeremy',
'tabs': [{
'tabId': 1
}],
'tasks': [{
'name': 'Im Jeremy Lin'
}],
'points': 50
}]
}, {
'uId': 2,
'name': 'justin',
'relationship': [{
'uId': 3,
'name': 'Jordan',
'tabs': [{
'tabId': 2
}],
'tasks': [{
'name': 'Im Jasmin Jacob'
}],
'points': 100
}]
}];
})
In your example there is only 1 relationship in each of the relationship arrays. If you add items to those arrays, you can see that they are being sorted correctly. Here is an update of your fiddle, with extra data for visibility:
http://jsfiddle.net/8Ub6n/
$scope.user = [
{
'uId':1,
'name':'Joe',
'relationship':[
{
'uId':2,
'name':'Jeremy',
'points':50
},
{
'uId':2,
'name':'Michael',
'points':80
}
]
},
{
'uId':2,
'name':'justin',
'relationship':[
{
'uId':3,
'name':'Jordan',
'points':100
},
{
'uId':4,
'name':'Cameron',
'points':15
}
]
}
];
You can do this in nested loop by:
<ul ng-repeat="friend in user | orderBy:'relationship':'points':true">
<li ng-repeat="relation in friend.relationship">
{{relation.name}}
</li>
</ul>
Here orderBy:'Array':'expression':reverse

Categories