Group data within a one-dimension list using Angular - javascript

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.

Related

Searching through multiple ng-repeats at once

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>

Angularjs: select not working when ng-model a json object

Once I use ng-options for a select I am unable to get dafault selection to work on nested json objects.
once I have a bit more complicated json and the select should handle a child object my select does not default select the proper value.
Given test = {"id":3,"title":"Test","product":{"id":4,"name":"Test1"}} as my ng-model test.product and
[{
"id": 4,
"name": "Test1"
}, {
"id": 5,
"name": "Test2"
}]
as my selection option. (see http://embed.plnkr.co/mpnislw77UBSEdHl4UKN/)
I seem to be unable to figure out how to facilitate default selection.
If you use track by item.id it works - http://embed.plnkr.co/mpnislw77UBSEdHl4UKN. The marked answer was not very obious since the ng-model is nested in iself. but it contains the correct information.
The only problem with your code is that you've assigned a new object to $scope.test.product and you're using it as the ng-model of the dropdown.
This makes AngularJS unable to find it inside the possible values, which are $scope.testarray. AngularJS will compare two objects by their reference, which you broke when you assigned a new object to $scope.test.product.
To make it working, change $scope.test as follows:
$scope.test = {
"id": 3,
"title": "Test",
"product": $scope.testarray[1]
}
This is how to select with ngOptions and setting a default value
Example
angular.module('defaultValueSelect', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.data = {
availableOptions: [
{id: '1', name: 'Option A'},
{id: '2', name: 'Option B'},
{id: '3', name: 'Option C'}
],
selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
};
}]);
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body ng-app="defaultValueSelect">
<div ng-controller="ExampleController">
<form name="myForm">
<label for="mySelect">Make a choice:</label>
<select name="mySelect" id="mySelect"
ng-options="option.name for option in data.availableOptions track by option.id"
ng-model="data.selectedOption"></select>
</form>
<hr>
<tt>option = {{data.selectedOption}}</tt><br/>
</div>
</body>
</html>
try this, hope it will help
<option value="" ng-if="false"></option>

Sort based on array length within array of objects in Angular JS

Consider the following example,
var arrayOfObject = [
{name: 'ron', data: [1,3,5]},
{name: 'raj', data: [2,3]},
{name: 'roy', data: [1]}
]
In the view, I need to sort the objects in ascending order based on the length of data array in each objects.
In the above example roy, raj, ron.
I could always loop through the array, find the length and sort it, But was wondering if there was a way to sort it using Angular's OrderBy Filter (view or controller).
Thanks,
Yes your can use angular's OrderBy filter.
In view:
<div ng-repeat="item in arrayOfObject | orderBy:'data.length'">
Or in controller:
var ordered = $filter("orderBy")(arrayOfObject, "data.length");
See this jsfiddle
Try this
<div ng-repeat="item in arrayOfObject | orderBy:'data.length'">
{{item.name}}:{{item.data.length}}
</div>
var app = angular.module("app", []);
app.controller("ctrl", function($scope) {
$scope.arrayOfObject = [{
name: 'ron',
data: [1, 3, 5]
},
{
name: 'raj',
data: [2, 3]
},
{
name: 'roy',
data: [1]
}
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
ASEC:
<div ng-repeat="item in arrayOfObject | orderBy:'data.length'">
{{item.name}}:{{item.data.length}}
</div>
<br/>
DESC:
<div ng-repeat="item in arrayOfObject | orderBy:-'data.length'">
{{item.name}}:{{item.data.length}}
</div>
</div>
<div ng-repeat="item in array| orderBy:'data.length'>
here orderBy takes property of data
We can use orderby clause
For Ascending
<div ng-repeat="item in arrayOfObject | orderBy:'data.length'">
{{item.name}}:{{item.data.length}}
</div>
For Descending order we need to use '-' sign (inside single quotes)
<div ng-repeat="item in arrayOfObject | orderBy:'-data.length'">
{{item.name}}:{{item.data.length}}
</div>
Try the following:
var myApp = angular.module('myApp', []);
myApp.controller('myController', ['$scope', '$filter', function($scope, $filter){
var arrayOfObject = [
{name: 'ron', data: [1,3,5]},
{name: 'raj', data: [2,3]},
{name: 'roy', data: [1]}
]
$scope.arrayOfObject = $filter("orderBy")(arrayOfObject, 'data.length');
console.log($scope.arrayOfObject);
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myApp' ng-controller='myController'>
</div>

how to count the checkbox which was checked using angular?

Here, I used $watch to display the counter value.
But the counter value not increased yet,
To Count,
$scope.$watch('items', function(items){
var selectedItems = 0;
angular.forEach(items, function(item){
selectedItems += item.selected ? 1 : 0;
})
$scope.selectedItems = selectedItems;
}, true);
In UI,
<div class="col-sm-4" ng-repeat="item in items">
<label class="chkbox-holder cbox mbot10" for="List-{{$index}}"><input ng-model="item.Selected" type="checkbox" id="List-{{$index}}"><label for="List-{{$index}}"></label>{{item.name}}</label>
</div>
To display the counter value,
<div>Selected Items Length: {{selectedItems}}</div>
But still the counter is 0;
JSON Value from http service is,
[
{
"id": 1,
"refCode": "",
"name": "pragadees"
},
{
"id": 2,
"refCode": "",
"name": "pragadees"
}......]
Can anyone please help on this.
You've just got a typo error. Your markup is bound to item.Selected while your JavaScript is checking for item.selected. Renaming them properly solves your problem. I'd recommend using lower key inside ot html.
<input ng-model="item.selected" type="checkbox" id="List-{{$index}}">
^-- See here
Demo
first of all, you got a typo in your ng-model, it is item.selected not item.Selected that's maybe why it does not match nothing...
And this is how i'll write it, using basic angular two way data binding, no extra counters, watches, triggers, ....
angular.module('myApp', []);
angular.module('myApp').controller('Ctrl', ['$scope', function($scope) {
$scope.items = [{
name: "Doe"
}, {
name: "Adam"
}, {
name: "Ado"
}, {
name: "Brown"
}, {
name: "Heather"
}, {
name: "Stan"
}];
$scope.itemsSelected = function() {
return $scope.items.filter(function(i) {
return i.selected
}).length;
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp" ng-controller="Ctrl">
<h2>current active selected options {{ itemsSelected() }}</h2>
<ul>
<div ng-repeat="item in items">
<label for="List-{{$index}}">
<input ng-model="item.selected" type="checkbox" id="List-{{$index}}">
<label for="List-{{$index}}"></label>{{item.name}}</label>
</div>
</ul>
</body>
You can use ng-change instead:
var app = angular.module("app", []);
app.controller("myCtrl", function($scope) {
$scope.items = [
{name: 'A', value: 'a'},
{name: 'B', value: 'b'},
{name: 'C', value: 'c'},
{name: 'D', value: 'd'},
{name: 'E', value: 'e'}
];
$scope.counter = 0;
$scope.change = function(e) {
if(e){
$scope.counter +=1;
}else{
if($scope.counter > 0) $scope.counter -=1;
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="myCtrl">
<div class="col-sm-4" ng-repeat="item in items">
<label class="chkbox-holder cbox mbot10" for="List-{{$index}}">
<input ng-model="item.Selected" type="checkbox" id="List-{{$index}}"
ng-change="change(item.Selected)">
<label for="List-{{$index}}"></label>{{item.name}}</label>
</div>
Counter: {{counter}}
</div>

Dependent Select Angular JS

I have hierarchical data set. There is one fixed root unit.
What I want to do is to make this tree browsable with dependent selects.
I have created a simple plunkr example with a fixed dataset.
http://plnkr.co/edit/Bz5A1cbDLmcjoHbs5PID?p=preview
The data format in the example mimics the format I would get from a server request in "real" life.
This working fine in this simple first step. What is missing is, that when a user changes a selection somewhere in the middle, the select boxes and the ng-model binding below the new selection need to be destroyed.
So when I select Europe->France->Quimper and change "Europe" to "Asia" - then there should be "Asia" as the first select box and a second one the Asia countries.
Is there an "Angular" way to deal to deal with this? Any other hint is appreciated also ;)
<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="bootstrap#3.3.5" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<script src="https://code.angularjs.org/1.3.17/angular.js" data-semver="1.3.17" data-require="angular.js#1.3.17"></script>
</head>
<body>
<div ng-controller="Ctrl">
<select ng-repeat="select in selects track by $index" ng-model="$parent.boxes[$index]">
<option ng-repeat="child in select.children" ng-click="expandSelects(child)">{{child.name}}</option>
</select>
<ul>
<li ng-repeat="item in boxes">{{ item }}</li>
</ul>
</div>
<script>
var app = angular.module('app', []);
app.controller('Ctrl', ['$scope', function($scope) {
var data = {
'europe': {
name: 'europe',
children: [{
name: 'france',
parent: 'europe'
}, {
name: 'italy',
parent: 'europe'
}],
},
'asia': {
name: 'asia',
children: [{
name: 'japan',
parent: 'asia'
}, {
name: 'china',
parent: 'asia'
}],
},
'france': {
name: 'france',
children: [{
name: 'paris',
parent: 'france'
}, {
name: 'quimper',
parent: 'france'
}]
}
};
var root = {
name: 'world',
children: [{
name: 'europe',
parent: 'world'
}, {
name: 'asia',
parent: 'world'
}, ]
};
$scope.selects = [root];
$scope.expandSelects = function(item) {
var select = data[item.name];
if (select) {
$scope.selects.push(select);
}
}
$scope.$watch('boxes', function(item, old) {
}, true);
}]);
</script>
</body>
</html>
This is a classic example of cascading dropdowns, with the added challenge of an unknown number of levels in the cascade. I combined the data set into one object for simplicity, added labels for the dropdowns, and simplified the select element.
This solution allows for any number of levels, so if you needed data below the city level, you could add it without changing any code, as illustrated by the "Street" example I added to Paris.
select {
display: block;
}
<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="bootstrap#3.3.5" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<script src="https://code.angularjs.org/1.3.17/angular.js" data-semver="1.3.17" data-require="angular.js#1.3.17"></script>
</head>
<body>
<div ng-controller="Ctrl">
<div ng-repeat="select in selects track by $index" ng-if="select.children">
<label>{{ select.optionType }}</label>
<select ng-model="selects[$index + 1]" ng-options="child.name for child in select.children" ng-change="clearChildren($index)"></select>
<hr />
</div>
</div>
<script>
var app = angular.module('app', []);
app.controller('Ctrl', ['$scope', function($scope) {
var data = {
optionType: 'Continent',
name: 'World',
children: [
{
optionType: 'Country',
name: 'Europe',
children: [
{
optionType: 'City',
name: 'France',
children: [
{
optionType: 'Street',
name: 'Paris',
children: [
{
name: 'First'
},
{
name: 'Second'
}
]
},
{
name: 'Quimper'
}
]
},
{
name: 'Italy'
}
]
},
{
optionType: 'Country',
name: 'Asia',
children: [
{
name: 'Japan'
},
{
name: 'China'
}
]
}
]
};
$scope.selects = [data]
$scope.clearChildren = function (index) {
$scope.selects.length = index + 2;
};
}]);
</script>
</body>
</html>
To go to the children in your hierachy is not as hard as it may seem. If you set up your select with angular and let it do most of the selection for you (for example using ng-options instead of ng-repeating the tag itself), and tell it what options there are, then the list of children you are trying to render just becomes a standard ng-repeat of the children that were picked from the select above.
I modified your plunker to show you how you could accomplish that a slightly different way.
http://plnkr.co/edit/zByFaVKWqAqlR9ulxEBt?p=preview
Main points I changed were
$scope.expandSelects = function() {
var select = data[$scope.selected.name];
if (select) {
console.log('changed');
console.log(select);
$scope.chosen = select;
}
}
Here i just grab the chosen item which the will use. Then the ends up looking like.
<ul>
<li ng-repeat="item in chosen.children">{{ item.name }}</li>
</ul>
The only other set up that was really needed was setting up the with ng-options and giving it a model to bind to.
<select ng-options="child.name for child in selects.children"
ng-model="selected" ng-change="expandSelects()">
</select>
Use can use a filter on the second select to filter de options based on the previous selection.
For example, you can have a first selection to choose the continent:
<select ng-options="c for c in continents" ng-model="selectedContinent" ></select>
and a second selection for the coutries:
<select ng-options="c.name for c in countries | filter : {parent:selectedContinent}" ng-model="selectedCountry" ></select>
Made a fiddle with a simplified data structured just to show how the filter works: http://jsfiddle.net/marcosspn/oarL4n78/

Categories