Products have 4 different category. I want to show them into 3 section. How to do this with angularjs? I want to repeat ng-repeat based on product category.
Please check my plnkr: http://plnkr.co/edit/XdB2tv03RvYLrUsXFRbw?p=preview
var product = [
{
"name": "Item 1",
"dimension": {
"height": "4 in",
"width": "3 in"
},
"category": "A"
}, {
"name": "Item 2",
"dimension": {
"height": "2 in",
"width": "6 in"
},
"category": "B"
}, {
"name": "Item 3",
"dimension": {
"height": "2 in",
"width": "3 in"
},
"category": "C"
}, {
"name": "Item 4",
"dimension": {
"height": "5 in",
"width": "2 in"
},
"category": "D"
}
];
You can use a filter :
ng-repeat="item in output.products | filter:{ category: 'A'}"
Edit : looks like everybody googled the same thig and found the same other StackOverflow answer ^^ #OP you should learn how to Google, btw !
Edit 2 : Reread the question, you need a custom filter :
app.filter('category', function() {
return function(items, categories) {
if (!items || items.length === 0) {
return [];
}
return items.filter(function(item) {
return categories.indexOf(item.category) > -1;
});
};
});
Use it like follows :
ng-repeat="item in output.products | category:['A', 'B']"
Edit 3 : be aware, though, that this can be pretty expensive in terms of performances for huge arrays. If you're dealing with such arrays, I suggest pre-filtering the data into several subarrays.
You can use angular.filter module.
You will have to define your own map function in the controller:
$scope.group = function(elem){
if(elem.category == 'C' || elem.category=='D'){
elem.category = 'C OR D' ;
return elem;
}
else return elem;
}
then in the html
<ul ng-repeat="(key, value) in products | map: group | groupBy: 'category'">
Group name: {{ key }}
<li ng-repeat="product in value">
player: {{ product.name }}
</li>
</ul>
note that you are loosing the information on the category of the element if it is C Or D, thing that won't happen if you use LoremIpsum's answer, but with this solution you will be able to create whatever groups you want.
here is a js fiddle with an example.
for example <div class="" ng-repeat="item in output.products | filter: {category:'A'}"> Whould only repeat items with Category A. You could also filter with a self defined function or other criteria.
You can use ng.if for this to.
Check here http://plnkr.co/edit/SKfUKTtKhUnZqec3ABSt?p=preview
<div class="" ng-repeat="item in output.products" ng-if='item.category =="A"'>
Related
I'm new to Handlebars and using version 4.1.2. I'm trying to move some templates which were written in PHP to Handlebars.
The source of my data is a JSON feed and the structure is like this:
[
{
"regulations_label": "Europe",
"groups_label": "Group 1",
"filters_label: "FF1A"
},
{
"regulations_label": "Europe",
"groups_label": "Group 1",
"filters_label: "FF1B"
},
{
"regulations_label": "Europe",
"groups_label": "Group 2",
"filters_label: "FF2A"
},
{
"regulations_label": "Asia",
"groups_label": "Group 999",
"filters_label: "FF999A"
},
{
"regulations_label": "Asia",
"groups_label": "Group 999",
"filters_label: "FF999B"
},
{
"regulations_label": "Americas",
"groups_label": "Group 10000",
"filters_label: "FF10000A"
},
]
The output of my HTML template (in the PHP version) was as follows:
Europe
Group 1
FF1A
FF1B
Group 2
FF2A
Asia
Group 999
FF999A
FF999B
Americas
Group 10000
FF10000A
The way in which this was achieved - without duplicating any of the regulations_label or groups_label during output - was to use conditional logic which checked the previous array value to see if it had changed, e.g.
// $data is the JSON above.
foreach ($data as $key => $d):
if ($data[$key-1]['groups_label'] !== $d['groups_label'])
echo $d['groups_label'];
endif;
endforeach;
The above code means that groups_label is only rendered if it is not the same as the previous value, i.e. it can only print "Group 1" once, etc.
So in Handlebars I'm wanting to apply the same principle. I've read Handlebars/Mustache - Is there a built in way to loop through the properties of an object? and understand there is a {{#index}} and {{#key}}.
The problem I'm having is that I can't apply conditional logic on these. For example there is no such thing as {{#index - 1}}
The way in which I have it set up is as follows:
<script id="regulations-template" type="text/x-handlebars-template">
{{#each myregs}}
- {{regulations_label}} <br>
-- {{groups_label}} <br>
---- {{filters_label}} <br>
{{/each}}
</script>
<script type="text/javascript">
var regsInfo = $('#regulations-template').html();
var template = Handlebars.compile(regsInfo);
var regsData = template({ myregs: // JSON data shown above });
$('#output').html(regsData);
</script>
<div id="output"></div>
The content is rendered to the #output div, but it repeats each level, e.g.
- Europe
-- Group 1
---- FF1A
- Europe
-- Group 1
---- FF1B
This problem is only happening because I'm unable to find a way to see what the previous array value was and do conditional logic. How can I solve this problem?
Notes for bounty:
I'm looking for a solution which uses Handlebars and takes advantage of any features it offers that can assist with this. Someone commented that it's possible to do this kind of thing in plain js/jquery. We want to use Handlebars to take advantage of the templates it offers therefore a solution which uses it fully is needed for the bounty. This sort of conditional logic is a trivial part of many other templating systems (not just limited to PHP). Therefore I can't help but think there's a way in Handlebars given templating is the main use case.
What you could do in a pure handlebars solution is to use the following helpers:
<script id="regulations-template" type="text/x-handlebars-template">
{{#each myregs}}
{{#ifPrevRegulation}} - {{regulations_label}} <br> {{/ifPrevRegulation}}
{{#ifPrevGroup}}-- {{groups_label}} <br>{{/ifPrevGroup}}
{{#ifPrevFilter}} ---- {{filters_label}} <br>{{/ifPrevFilter}}
{{setPrevContext}}
{{/each}}
</script>
Where
var prevContext = null;
Handlebars.registerHelper('setPrevContext', function(){
prevContext = this;
});
Handlebars.registerHelper('ifPrevRegulation', function(options){
if(!(prevContext && this.regulations_label == prevContext.regulations_label)){
return options.fn(this);
}
});
Handlebars.registerHelper('ifPrevGroup', function(options){
if(!(prevContext && this.regulations_label == prevContext.regulations_label && this.groups_label== prevContext.groups_label)){
return options.fn(this);
}
});
Handlebars.registerHelper('ifPrevFilter', function(options){
if(!(prevContext && this.regulations_label == prevContext.regulations_label && this.groups_label== prevContext.groups_label && this.filters_label == prevContext.filters_label)){
return options.fn(this);
}
});
I have used a recursive function (prepare for a loooong one liner) to format the data in such a way that should be easier to display. You can just map over the children, and use the labels to print:
const data = [
{ regulations_label: "Europe", groups_label: "Group 1", filters_label: "FF1A" },
{ regulations_label: "Europe", groups_label: "Group 1", filters_label: "FF1B" },
{ regulations_label: "Europe", groups_label: "Group 2", filters_label: "FF2A" },
{ regulations_label: "Asia", groups_label: "Group 999", filters_label: "FF999A" },
{ regulations_label: "Asia", groups_label: "Group 999", filters_label: "FF999B" },
{ regulations_label: "Americas", groups_label: "Group 10000", filters_label: "FF10000A" }
];
const r = (o, keys) => Object.entries(o.reduce((a, { [keys[0]]: l, ...r }) => ({ ...a, [l]: a[l] ? [...a[l], r] : [r] }), {})).map(([k, v]) => ({ label: k, children: keys.length === 2 ? v.map(o => o[keys[1]]) : r(v, keys.slice(1))}))
const myregs = r(data, ['regulations_label', 'groups_label', 'filters_label'])
const log = () => console.log(myregs)
var regsInfo = document.getElementById('regulations-template').innerHTML
var template = Handlebars.compile(regsInfo);
var regsData = template({myregs});
document.getElementById('output').innerHTML = regsData
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.1.2/handlebars.js"></script>
<script id="regulations-template" type="text/x-handlebars-template">
{{#each myregs}}
- {{this.label}} <br>
{{#each this.children}}
- - {{this.label}} <br>
{{#each this.children}}
- - - {{this}} <br>
{{/each}}
{{/each}}
{{/each}}
</script>
<button onclick="log()">Log data</button>
<div id="output"></div>
Note I removed the jQuery, just because I felt it wasn't needed, but feel free to add it back in :)
I'm trying to applay a filter in my ng-repeat based on a nested array. The object that is used for the ng-repeat:
Updated
master_array:
{
"0": {
"Employee_Id": "hni",
"comptencies": [
{
"Title": "Bunker Knowledge",
"Level": 1
},
{
"Title": "Bunker Knowledge",
"Level": 3
},
{
"Title": "IT Compliance",
"Level": 2
},
{
"Title": "Bunker Knowledge",
"Level": 5
}
],
}
}
JS:
$scope.myFilter = {
competencyTitle : ""
}
HTML:
<label>Competencies
<select ng-model="myFilter.competencyTitle" ng-options="item.Title for item in competencies_list"></select>
</label>
<tr ng-repeat="item in master_array | filter: { competencies: [{ Competency.Title: myFilter.competencyTitle }] }">
The above doesn't work.
Case
I have a list of employees and each employee has an id and array of competencies attached. Each item in this array has a comptency array attached, which holds the title an id of the competency. What I want is to be able to filter out employees with specific competencies.
Any suggestions?
I found a solution. You probably have to adapt it to your specific code:
The main idea is this: filter: { $: {Title: myFilter.competencyTitle.Title} }
The $ selects any element whose child is Title.
html
<label>Competencies {{myFilter.competencyTitle.Title}}
<select ng-model="myFilter.competencyTitle" ng-options="item.Title for item in competencies_list"></select>
</label>
<p ng-repeat="item in master_array | filter: { $: {Title: myFilter.competencyTitle.Title} }">{{item.employee_id}} {{item.competencies}} </p>
js - this is the model is used, I'm not sure if this reflects your actual model.
$scope.competencies_list = [{Title : "Math"},{Title : "English"},{Title : "Spanish"}];
$scope.master_array =
[
{
employee_id: "hni",
competencies:
[
{Title : "Math", "Level": 1},
{Title : "English", "Level": 2}
]
},
{
employee_id: "xyz",
competencies:
[
{Title : "Spanish", "Level": 3},
{Title : "English", "Level": 5}
]
}
];
$scope.myFilter = {
competencyTitle : ""
};
so I'm working with a basic product category model to get my head around filtering and I can't figure out how to extract a property value from one object within an array while repeating through another.
A simplified version of my category array, which is in scope, looks like this. I can output their names with the preceding directive and the results are as expected:
[{
"_id": "TY76",
"name": "Planes"
}, {
"_id": "887T",
"name": "Trains"
}, {
"_id": "A0K4",
"name": "Autos"
}]
<p ng-repeat="category in product.categories "> {{ category.name }}</p>
And here is a simplified product, also in scope, which may contain the ID of one or more categories. In this case, Bobble Head belongs to both Planes and Autos:
{
"_id": "9876",
"name": "Bobble Head",
"cats": "['TY76','A0K4']"
}
Now, here is where I'm having a hard time. I need to output the category names with the product. I can output the IDs no problem via:
<p ng-repeat="cat in product.cats ">{{ cat }}</p>
But that's of no use to the end user but I have no idea how to end up with something like:
Product: Bobble Head | Categories: Planes, Autos
I don't have the autonomy to add the category name to each product and I've tried a bunch of different filtering approaches but I don't think I'm wording my question right or something because I'm not finding much on the interwebs about this.
Any ideas?
Sounds like you want to build up a lookup for category id to category name:
var categories = [{
"_id": "TY76",
"name": "Planes"
}, {
"_id": "887T",
"name": "Trains"
}, {
"_id": "A0K4",
"name": "Autos"
}];
// build a category lookup id -> name
var categoryLookup = {};
categories.forEach(function(category) {
categoryLookup[category._id] = category.name;
});
Here's a full working fiddle: http://jsfiddle.net/02qadem7/1/
You can create a key-pair object where the key is the id and the value is the name of the category:
var categoriesArray = [{
"_id": "TY76",
"name": "Planes"
}, {
"_id": "887T",
"name": "Trains"
}, {
"_id": "A0K4",
"name": "Autos"
}];
$scope.categoriesMap = {};
categoriesArray.forEach(function(category) {
$scope.categoriesMap[category._id] = category.name;
});
Then in your view you can access the category name like this:
<div ng-repeat="product in products">
<strong>Product: </strong> {{product.name}} |
<strong>Categories: </strong> <span ng-repeat="category in product.cats">
{{categoriesMap[category]}}
</span>
</div>
Here's a plunkr: http://plnkr.co/edit/BpBcCizzU2Vh8VPniiHA?p=preview
I sugest using a custom filter on categories array.
myApp.filter('byCategoryIds', function() {
return function(categories, catIds) {
return categories.filter(function(item) {
return (catIds.indexOf(item._id) != -1);
});
};
});
Then you can iterate on categori array sending ids array like so:
<b>Product:</b>
{{product.name}};
<b>Categories:</b>
<span ng-repeat="cat in categories | byCategoryIds: product.cats">{{ cat.name }}, </span>
my object looks like this:
$scope.options = [{
name: 'John',
phone: '555-1212',
age: 10,
descriptions: [{
"languageId": "EN",
"description": "b Some description",
}, {
"languageId": "DE",
"description": "b Some description in dutch",
}]
},
{
name: 'Jimmy',
phone: '555-1212',
age: 10,
descriptions: [{
"languageId": "EN",
"description": " d Some description",
}, {
"languageId": "DE",
"description": "d Some description in dutch",
}]
},
{
name: 'Cris',
phone: '555-1212',
age: 10,
descriptions: [{
"languageId": "EN",
"description": "a Some description",
}, {
"languageId": "DE",
"description": "a Some description in dutch",
}]
}]
I want to sort this according to the field "description" in side the "descriptions",
I'm able to sort this by other fields such as name, phone and age, but not by
description.
<tr ng-repeat="option in options | orderBy:'name':true">
<td> {{option.name}}</td>
<td ng-repeat="desc in option.descriptions |filter:{languageId:'EN'} ">{{desc.description}}</td>
</tr>
Please suggest me a way to sort the data by "description".
You can use ngInit to prefilter description in given language and then use it for sorting and displaying:
<tr ng-repeat="option in options | orderBy:'description'"
ng-init="option.description = (option.descriptions | filter:{languageId:'EN'})[0].description">
<td>{{option.name}}</td>
<td>{{option.description}}</td>
</tr>
Demo: http://plnkr.co/edit/eyRYqfeJ6ewaJnGReGTB?p=preview
You can write a custom sort that can do the trick, The second argument in orderby can taka a string, function or an array Refer Angular order By
<tr ng-repeat="option in options | orderBy:myCustomSort :true">
<td> {{option.name}}</td>
<td ng-repeat="desc in option.descriptions |filter:{languageId:'EN'} ">
{{desc.description}}
</td>
</tr>
And your JS I guess you can put a variable which language is currently on, and this should do the trick
//inside your controller
$scope.lang = 'EN'; //you can edit this the way you added a filter:'EN'
$scope.myCustomSort = function(opt){
for(var i=0; i<opt.descriptions.length; i++){
//written assuming you have one description that is for 'EN'
//you can sort out the description array first and then return
if(opt.descriptions[i].languageId === $scope.lang){
return opt.descriptions[i].description.toUpperCase();
}
}
return '';
}
Oh and one more thing your description [2] there is a blank space that was messing and took a long time to figure out why ... "description": " d Some description", The blank will be treated first **rules of sorting :)
wouldn't it be same as outside ng-repeat
<td ng-repeat="desc in option.descriptions | orderBy:'description':true |filter:{languageId:'EN'} ">
So take this example:
http://jsfiddle.net/7U5Pt/
Here I've got an object that describes a (very basic) menu. Each item can have multiple children with potentially unlimited levels of hierarchy.
The ng-repeat directives I'm using to turn it into <ul><li> elements work fine for the first two levels, but not for the third or any subsequent level of the hierarchy.
What's the best way to recursively iterate over this object, dealing with unlimited levels of children?
Any help much appreciated!
Here's the code incase the fiddle goes away:
HTML:
<div ng-app="myApp">
<div ng-controller="myCtrl">
<nav class="nav-left">
<ul ng-repeat="item in mytree.items">
<li>NAME: {{ item.name }}
<ul ng-repeat="item in item.children.items">
<li>SUB NAME: {{ item.name }}</li>
</ul>
</li>
</ul>
</nav>
</div>
JS:
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl', function ($scope) {
$scope.mytree = {
"items": [{
"name": "one",
"children": {
"items": [
{
"name": "one sub a",
"children": {
"items": [{
"name": "one sub level two a"
},
{
"name": "one sub level two b"
}]
}
},
{
"name": "one sub b"
}
]
}
},
{
"name": "two"
},
{
"name": "three"
},
{
"name": "four",
"children": {
"items": [{
"name": "four sub a"
},
{
"name": "four sub b"
}]
}
},
{
"name": "five"
}]
};
});
So it turns out that the best way to do this, for anyone interested, is with the angular-recursion helper here:
https://github.com/marklagendijk/angular-recursion
This allows you to call the ng-repeat function recursively. So in your view, something like:
<tree family="mytree"></tree>
and then define a directive for that calls itself like so:
.directive('tree', function(RecursionHelper) {
return {
restrict: "E",
scope: {family: '='},
template:
'{{ family.name }}'+
'<ul ng-if="family.children">' +
'<li ng-repeat="child in family.children">' +
'<tree family="child"></tree>' +
'</li>' +
'</ul>',
compile: function(element) {
return RecursionHelper.compile(element);
}
};
});
Apparently the recursion helper is necessary to stop some sort of infinite loop thing, as discussed here. Thanks to #arturgrzesiak for the link!