I have the following JSON structure in my Angular app:
Sample: http://pastie.org/pastes/9476207/text?key=u6mobe15chwyiz1jakn0w
And the following HTML which is displaying every product in the parent array:
<div class="bb-product-grid" ng-repeat="brand in notebooks">
<ul ng-repeat="products in brand">
<li ng-repeat="Notebook in products">
<span class="product-title">{{Notebook.Model}}</span>
<span class="product-description"><p>{{Notebook.Description}}</p></span>
<span class="product-image">image</span>
<span class="product-add-to-bundle"><button>Add to bundle</button></span>
<span class="product-price">{{Notebook.Price}}<sub>pcm</sub></span>
</li>
</ul>
</div>
I want to be able to filter the products by the brand names (array names, Acer, Apple etc.).
By default, only the 'Apple' products will be visible. You will then click a button to change the visible results to reflect what brand you've selected. So if you then select HP, you'll only see 'HP' products.
I'm a little stuck on this because the JSON structure is pretty nested, if it was 1/2 levels I'd be able to cope but I can't figure this out, I seem to only show all the products or break the app. Any advice on the best-practise approach to this (I'm guessing I might have to create a custom filter?) will be greatly appreciated!
If you can get your data to be in this format:
$scope.data = [
{
brand: 'Acer',
laptops: ['acer_1', 'acer_2', 'acer_3', 'acer_4', 'acer_5']
},
{
brand: 'Apple',
laptops: ['apple_1', 'apple_2', 'apple_3', 'apple_4', 'apple_5']
},
{
brand: 'Asus',
laptops: ['asus_1', 'asus_2', 'asus_3', 'asus_4', 'asus_5']
},
{
brand: 'HP',
laptops: ['hp_1', 'hp_2', 'hp_3', 'hp_4', 'hp_5']
},
{
brand: 'Lenovo',
laptops: ['lenovo_1', 'lenovo_2', 'lenovo_3', 'lenovo_4', 'lenovo_5']
},
{
brand: 'Toshiba',
laptops: ['toshiba_1', 'toshiba_2', 'toshiba_3', 'toshiba_4', 'toshiba_5']
}
];
Then you can use have a filter like so:
$scope.search = {
brand: 'HP'
};
With HTML:
<select ng-model="search.brand">
<option ng-repeat="company in data">{{company.brand}}</option>
</select>
<ul ng-repeat="company in data | filter:search">
<li><b ng-bind="company.brand"></b></li>
<ul ng-repeat="laptop in company.laptops">
<li ng-bind="laptop"></li>
</ul>
</ul>
Here is a jsfiddle.
brands is an array of objects with the brand names as keys. It doesn't make sense to use filters to solve this problem ... however it's easy to get the products if you have the key name. Imagine you have searchName = 'Apple' and obj = [{Apple: []}, {Acer: []}]. You could just use obj[0].Apple.
this.filterItem = "Apple";
this.productsToShow = productObject[0][this.filterItem];
<select ng-model=ctrl.filterItem><!-- brand names go here --></select>
<ul ng-repeat="product in ctrl.productsToShow">
Don't use filter, when only ever one is supposed to be visisble. Instead, skip the iteration over the brands, and instead just iterate over the selected brand. All you need to do is then make sure you set selectedBrand to the correct subset of your data structure.
Fiddle: http://jsfiddle.net/5wwboysu/2/
Show brand:
<span ng-repeat="(name,brand) in notebooks.Notebooks[0]" ng-click="selected(brand)">
{{name}} - <br>
</span>
<ul ng-repeat="products in selectedBrand">
<!-- snip -->
</ul>
$scope.selected = function(brand) {
$scope.selectedBrand = brand;
};
However, if you have control over the JSON, I'd try to return it in a format better suited for what you're trying to do.
Add this filter which will take a brand:
<ul ng-repeat="brand in notebooks| filter:filterBrand">
in controller
$scope.SelectedBrandName= "Apple";
$scope.filterBrand = function(brand){
if(brand.Name == $scope.SelectedBrandName)
return true;
return false;
};
This will look at each brand and only allow brands that return true. If you have a button that changes the $scope.SelectedBrandName value to a different name, it will automatically filter through the ng-repeat to only brands that match.
Related
I have some json data that I output with ng-repeat and I have added some form controls to filter data output from another json.
Simplified sample
First JSON data:
var technologies = [
{"id":"5", "slug":"mysql", "label":"MySQL", "category":"database"},
{"id":"4", "slug":"html", "label":"HTML", "category":"markup"}
]
and the output:
<ul>
<li ng-repeat="tech in technologies">
<span>{{tech.label}}</span>
<span><label>required expertise
<select>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select></label>
</span>
</li>
</ul>
(actually, the technologies are at this point already filtered from another choice that the user makes on a different page, but I assume that makes no difference)
The 2nd json contains the expertise property that I want to use as a filter
var people = [
{
'label': 'MySQL',
'slug': 'mysql',
'talents':[
{
'name': 'Stanislav Lem',
'expertise': 5,
'desire': 0,
'last_used': '2009-01'
},
{
'name': 'Ijon Tichy',
'expertise': 1,
'desire': 5,
'last_used': '2011-06'
}
]
}, ...
]
...and that is for now being plain used as
<ul>
<li ng-repeat="item in people">
{{item.label}}<br>
<ul>
<li ng-repeat="person in item.talents">{{person.name}} · Expertise: {{person.expertise}}</li>
</ul>
</li>
</ul>
What I need is a filter that uses the options from the first output to filter the second output. For example when MySQL required expertise is set to '2', only Stanislav Lem will be displayed. Actually I will also need a 'no results found' message if the noone matches the expertise, but I guess I can figure that part out myself.
Plunker sample is here: http://plnkr.co/edit/B0aQp9aCJ2g4OM6fZ3qe?p=preview
Working Plunker: http://plnkr.co/edit/jcb6SfKVPkwt7FFCZFHX?p=preview
The first thing you'll need to do is add an ng-model attribute to the select. I've added this as a property of the technology so you can easily find it.
<select ng-model="tech.required">
Next, I added the following two functions to your controller to aid in filtering the second list:
$scope.getFilterForSlug = function(slug) {
var technology = getTechBySlug(slug);
return function(person) {
return person.expertise >= (technology.required || 0);
}
}
function getTechBySlug(slug) {
return _.find($scope.technologies, {
slug: slug
});
}
I'm using lodash in getTechBySlug, as the name states, to get the correct technology object based on the slug. Using that, getFilterForSlug returns a function which compares the person's expertise level with the desired level from the select. If an option wasn't selected, then the desired level is set to 0.
Lastly, in the HTML, I added the following:
<ul>
<li ng-repeat="person in filteredPeople = (item.talents | filter: getFilterForSlug(item.slug))">{{person.name}} · Expertise: {{person.expertise}}</li>
</ul>
<span ng-if='!filteredPeople.length'>No Results!</span>
This code gets a filter based on the current slug, and then filters the people using that function. It stores the results in a variable filteredPeople, which is used in the span to display a message when there are no results.
So I'm absolutely new to writing any type of code, I'm trying to build a simple website using AngularJS and I'm having a problem with looping through a controller's object array through a directive template. My goal is to output each array string into a separate list item element in the template,
for example in the controller I have
$scope.items = [
{
list: ["1", "2", "3"],
name: "whatever"
},
{
list: ["4", "5", "6", "7", "8"],
name: "whatever2"
}]
in the directive template I have written something like this
<h1>{{ directive.name }}</h1>
<ul id= "items"></ul>
<script>
for (var i = 0; i < directive.list[].length; i++) {
document.getElementById("items").innerHTML =
"<li>{{" + directive.list[i] + "}}</li>";
};
</script>
the name object retrieves correctly in index.html using the template but list object will not output the array, gave it my best shot, tried troubleshooting to get it right and I can't seem to figure it out, need help :( hope I'm being clear enough, this is my first attempt at creating anything and still trying to get familiar with all the jargon.
Here is a shot:
<div ng-repeat="i in items">
<h1>{{ i.name }}</h1>
<ul>
<li ng-repeat="item in i.list">{{i}} </li>
</ul>
</div>
And the description:
Basically you want to loop through all the items in your array <div ng-repeat.. then for each of those items you want to create the <ul>
It's simple -- replace your <script> with this:
<ul>
<li ng-repeat="item in items">{{ item.name }}
<ul id="items">
<li ng-repeat="list_item in item.list">{{ list_item }}</li>
</ul>
</li>
</ul>
I'm trying to use ng-repeat to repeat the rest of data if the attribute is the same. This may not sound clear so here is an example.
JSON sample, which is an array of books
[{"Genre":Sci-fic,"Name":"Bookname1"},
{"Genre":Sci-fic,"Name":"Bookname2"},
{"Genre":Non sci-fic,"Name":"Bookname3"},
{"Genre":Non sci-fic,"Name":"Bookname4"}]
So if the book belongs in the same genre, then we would have genre type as the heading of the accordion and list the rest of the attributes for the ones whose genre is that without repeating the heading. The problem I've been encountered is that since json array of objects comes from a data model, and I can't really filter the type without repeating the genre type.
here is what I'm having where I'm trying to have heading as the Book.Genre and inside that accordion-group will have the list of all the books whose genre is the heading.
http://plnkr.co/edit/PwIrOAIT3RRaE2ijzGZb?p=preview
I haven't been able to tackle this problem yet either... The best solution I can think of is to simply strip out the duplicates before adding that model to your scope...
Using your jsFiddle example...
http://jsfiddle.net/mcpDESIGNS/Uz5tM/1/
var books = [
{"Genre":"Sci-fic", "Name":"Bookname1"},
{"Genre":"Sci-fic", "Name":"Bookname2"},
{"Genre":"Non sci-fic", "Name":"Bookname3"},
{"Genre":"Non sci-fic", "Name":"Bookname4"}
];
var unique = [],
blocked = [];
unique.push(books[0]);
blocked.push(books[0].Genre);
for (var i = 1; i < books.length; i++) {
if (blocked.indexOf(books[i].Genre) <= -1) {
unique.push(books[i]);
blocked.push(books[i].Genre);
}
}
$scope.Books = books;
// [{Genre: "Sci-fic", Name: "Bookname1"},
// { Genre: "Non sci-fic", Name: "Bookname3" }];
it happens because you 've got duplicate value in generes use track by
<div ng-repeat="genere in generes track by $index"></div>
or if you want group you model by Genre you can use underscore.js please see that fiddle
View :
<div ng-app="app">
<div ng-controller="ParentCtrl">
<ul class="about-tab-titles" close-others="oneAtATime">
<li ng-repeat="(key ,book) in model">
{{key}}
<ul>
<li ng-repeat="b in book ">{{b.Name}}</li>
</ul>
</li>
</ul>
</div>
</div>
JS:
angular.module('app', [])
function ParentCtrl($scope) {
var books = [{
"Genre": "Sci-fic",
"Name": "Bookname1"
}, {
"Genre": "Sci-fic",
"Name": "Bookname2"
}, {
"Genre": "Non sci-fic",
"Name": "Bookname3"
}, {
"Genre": "Non sci-fic",
"Name": "Bookname4"
}];
$scope.model = _.groupBy(books, 'Genre');
}
So I found a solution which works for me. I followed this AngularJs filter on nested ng-repeat. I made a couple changes for my data model, but it's the same idea.
I have a list of products. Each product has three properties: mustBuy, promotion (both booleans) and productName. What I'm trying to do is to apply one filter at the time, based on the user input. If the user clicks on a button called "Must buy", the products will be filtered by the mustBuy field, so once I click that button I should only see the products that have a mustBuy property with a value of true.
I want to generate those filter buttons in a dynamic manner because I might add extra properties later on. Right now I have a list of hard coded buttons used for filtering:
<a ng-click="myFilter = {mustBuy:true}">
<img src="/images/must-filter.png" />
</a>
<a ng-click="myFilter = {promotion:true}">
<img src="/images/promo-filter.png" />
</a>
This is what "myFilter" filters.
<div ng-repeat="product in products | filter:myFilter">...</div>
It works fine but I want to make those filters dynamic as I'll add more in the future. This is what I've tried:
(v1):
Js controller:
$scope.filters = [{ image: "must-filter.png", myFilter: { mustBuy: true } }, { image: "promo-filter.png", myFilter: { promotion: true } }];
Html:
<a ng-repeat="f in filters | filter:f.myFilter">
<img src="/images/{{f.image}}" />
</a>
(v2):
Js controller:
$scope.filters = [{ image: "must-filter.png", myFilter: { mustBuy:true } }, { image: "promo-filter.png", myFilter: {promotion:true} }];
Html:
<a ng-repeat="f in filters"
ng-click="{{f.myFilter}}">
<img src="/images/{{f.image}}" />
</a>
(v3):
Js controller:
$scope.filters = [{ image: "must-filter.png", myFilter: "myFilter = {mustBuy:true}" }, { image: "promo-filter.png", myFilter: "myFilter = {promotion:true}" }];
Html:
<a ng-repeat="f in filters"
ng-click="{{f.myFilter}}">
<img src="/images/{{f.image}}" />
</a>
The third approach (v3) has EXACTLY the same HTML output the original (hardcoded) approach had, except it doesn't work, which makes me think there is something more (binding) happening behind the scenes? How can I achieve the functionality described in the first paragraph?
Without looking too much (read at all) why your attempts failed, you can achieve what you want like this:
1.)
Define a list of filters and the currently selected (applied) filter in the contoller:
$scope.filters = [
{name: 'none', filterExpr: ''},
{name: 'must buy', filterExpr: {mustBuy: true}},
{name: 'promotion', filterExpr: {promotion: true}}
];
$scope.selectedFilter = $scope.filters[0];
(My filter-object have a name and a filterExpr, but you can adjust them to your needs.)
2.)
Define a function for changing the applied filter (again in the contoller of course):
$scope.setFilter = function (filter) {
$scope.selectedFilter = filter;
};
3.)
Let the user change the applied filter (in the view):
<ul>
<li ng-repeat="filter in filters">
{{filter.name}}
</li>
</ul>
4.)
Filter the items, based on the currently selected filter's filter-expression:
<ul>
<li ng-repeat="item in items | filter:selectedFilter.filterExpr">
{{item.description}}
</li>
</ul>
See, also, this short demo.
As you can see in this FIDDLE, you must use a function or custom filter to chain your dynamic filters.
Filter set:
//this set will filter everything but true-false
$scope.set1 = [function(item){return item.mustBuy;},
function(item){return !item.promotion}];
Filter function:
$scope.multipleFilters = function(item){
var v = true;
for(var i=0; i<$scope.set1.length; i++){
v = v && $scope.set1[i](item);
}
return v;
};
Use like this:
<div ng-repeat="product in products | filter:multipleFilters">
{{product.mustBuy}} - {{product.promotion}}
</div>
The point is using functions for filters like function(item){return item.mustBuy}, not expressions like {mustBuy:true}
What am I doing wrong? I am trying to create a simple master details view a la the 'canonical MVVM' example.
Here's a simplified example in JSfiddle that doesn't work: http://jsfiddle.net/UJYXg/2/
I would expect to see the name of the selected 'item' in the textbox but instead it says 'observable'?
Here's my offending code:
var list = [ { name: "item 1"} , { name: "Item 2" }];
var viewModel = {
items : ko.observableArray(list),
selectedItem : ko.observable(),
}
viewModel.setItem = function(item) {
viewModel.selectedItem(item);
}
ko.applyBindings(viewModel);
And the HTML
<ul data-bind="foreach: items">
<li>
<button data-bind="click: $root.setItem, text:name"></button>
</li>
</ul>
<p>
<input data-bind="value:selectedItem.name" />
</p>
You are really close. Just need to do value: selectedItem().name or better use the with binding to change your scope. Also, the script that you are referencing is slightly out-of-date (in 2.0 click passes the data as the first arg).
Sample here: http://jsfiddle.net/rniemeyer/acUDH/