I'm using this AngularJS paginator, https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination.
And I'm also using the example in this video to add checkbox filters to my ng-repeat. I want to filter based on whether the specie property of every animal object (animal.specie) is a cat or a dog. I start setting both to true but my ng-repeat doesn't populate when I do that. And when I check a box to true or false nothing happens.
The only thing in the controller is $scope.species = {dogs: true, cats: true};
What is wrong with my implementation? Any help will be much appreciated.
Thanks :)
<div class="row adopt-title-wrap">
<div class="medium-6 large-6 column">
<h3>Find Your Next Best Friend</h3>
</div>
<div class="medium-2 large-2 end column">
<input type="checkbox" ng-model="species.cats">
<label>Cats Only</label>
</div>
<div class="medium-2 large-2 end column">
<input type="checkbox" ng-model="species.dogs">
<label>Dogs Only</label>
</div>
</div><br>
<i class="fa fa-spinner fa-pulse fa-3x"></i>
<div class="row">
<!-- Start Single Animal Record -->
<div class="medium-6 large-6 end column" dir-paginate="animal in animals
| filter: {specie: 'Dog'} : species.dogs
| filter: {specie: 'Cat'} : species.cats
| itemsPerPage: 6">
<div class="panel">
<img class="pet-img" ng-src="{{ animal.profile_photo }}" alt="pet for adoption">
<ul class="pet-details">
<li class="pet-field-title">Name</li>
<li class="pet-field-value">{{ animal.name }}</li>
<li class="pet-field-title">Age</li>
<li class="pet-field-value">{{ animal.dob }}</li>
<li class="pet-field-title">Breed</li>
<li class="pet-field-value">{{ animal.breed }}</li>
<li class="pet-field-title">Code</li>
<li class="pet-field-value">{{ animal.shelter_code }}</li>
</ul>
<p class="pet-description">{{ animal.description }}</p>
</div>
</div> <!-- End Single Animal Record -->
</div>
<dir-pagination-controls></dir-pagination-controls>
I would recommend stripping down your code to the base, maybe just get everything displayed and then migrate to pagination. I can spot your first issue is you are applying two filters on specie.
dir-paginate="animal in animals | filter: {specie: 'dog'} | filter: {specie: 'cat'}"
is basically saying filter down to animals that are both a dog and a cat. The first filter will return an array of only dogs, then the second filter will run on that finding only cats, which is none because obviously you can't be a dog and a cat :)
Original Array: [{name: 'bob', specie: 'cat'}, {name: 'jeff', specie: 'dog'}] | filter dogs | filter cats
After First Filter (only dogs): [{name: 'jeff', specie: 'dog'}] | filter cats
After Second Filter (only animals that are both dogs and cats): []
Try defining your own angular filter to simplify your work as so:
.filter('isCatOrDog', function() {
return function(inputAnimals) {
return inputAnimals.filter(function(animal) {
return animal.value === 'cat' || animal.value === 'dog';
});
}
});
And you call the filter as so:
dir-paginate="animal in animals | isCatOrDog"
Basic Plunkr
Just learned that Angular filters don't have (at least not yet) the functionality I was looking for. So I worked around the issue by changing the check boxes to radio buttons and make a different backend API call based on the radio selected.
template:
<div class="row adopt-title-wrap">
<div class="medium-5 large-5 column">
<h4>Find Your Next Best Friend!</h4>
</div>
<div class="medium-1 large-1 column">
<i class="fa fa-spinner fa-pulse fa-2x"></i>
</div>
<div class="medium-2 large-2 column">
<input type="radio" ng-model="species" value="all">
<label>Show All</label>
</div>
<div class="medium-2 large-2 column">
<input type="radio" ng-model="species" value="cats">
<label>Cats Only</label>
</div>
<div class="medium-2 large-2 column end">
<input type="radio" ng-model="species" value="dogs">
<label>Dogs Only</label>
</div>
</div>
<div class="row">
<!-- Start Single Animal Record -->
<div class="single-record medium-6 large-6 end column" dir-paginate="animal in animals | itemsPerPage: 6">
<div class="panel">
<img class="pet-img" ng-src="{{ animal.profile_photo }}" alt="pet for adoption">
<ul class="pet-details">
<li class="pet-field-title">Name</li>
<li class="pet-field-value">{{ animal.name }} ({{animal.specie }})</li>
<li class="pet-field-title">Age</li>
<li class="pet-field-value">{{ animal.dob }}</li>
<li class="pet-field-title">Breed</li>
<li class="pet-field-value">{{ animal.breed }}</li>
<li class="pet-field-title">Code</li>
<li class="pet-field-value">{{ animal.shelter_code }}</li>
</ul>
<p class="pet-description">{{ animal.description }}</p>
</div>
</div> <!-- End Single Animal Record -->
</div>
<dir-pagination-controls></dir-pagination-controls>
Controller:
angular.module('animalShelterApp')
.controller('AdoptCtrl', function ($scope, SharedServices) {
var spinner = angular.element('.fa-spinner'); // Holds spinner element.
/*
* Binds 3 radio buttons (all, cats, dogs).
* Load 'all' records by default.
*/
$scope.species = 'all';
/*
* When user selects a specie, display appropriate data.
*/
$scope.$watch('species', function() {
spinner.show();
if ($scope.species === 'all') {
/*
* Get all available animals.
*/
SharedServices.getAllAnimals().success(function(data) {
spinner.hide();
$scope.animals = data;
});
} else if ($scope.species === 'cats') {
/*
* Get all available Cats.
*/
SharedServices.getAllCats().success(function(data) {
spinner.hide();
$scope.animals = data;
});
} else if ($scope.species === 'dogs') {
/*
* Get all available Dogs.
*/
SharedServices.getAllDogs().success(function(data) {
spinner.hide();
$scope.animals = data;
});
}
}); // end $scope.$watch('species').
}); // end AdoptCtrl.
Related
I developed a gallery of images separated by several categories.
I just want to display categories that contain images.
I have three categories: "new", "old", "try".
Of these three categories, only new and old have images. My problem is that all categories are appearing, even those that have no image (as is the case with try).
Is there a way to present only the categories that contain images?
How can I do this?
DEMO
code
<div *ngFor="let cat of Cats">
<div class="row ">
<span class="">{{cat}}</span>
</div>
<ul class="mdc-image-list my-image-list" style="padding-left: 10px;padding-right: 10px;">
<ng-container *ngFor="let product of data; let j = index;">
<li class="mdc-image-list__item" *ngIf="product.Cat == cat">
<div class="mdc-image-list__image-aspect-container">
<ng-container *ngIf="product.image == null; else productImage">
<img src="./assets/image-not-found.svg" class="mdc-image-list__image imagenotfound">
</ng-container>
<ng-template #productImage>
<img [src]="product.image" class="mdc-image-list__image">
</ng-template>
</div>
</li>
</ng-container>
</ul>
</div>
The try should not be presented :(
You can wrap your category div in an <ng-container>, and then use *ngIf to check if your data array contains a product in a given category. The best way to achieve that is to have an array with product counts per given category.
Add this to your AppComponent class:
get counts() {
return this.data.reduce((obj, value) => {
if (value.Cat in obj) {
obj[value.Cat]++;
} else {
obj[value.Cat] = 1;
}
return obj;
}, {});
}
And then use this as your template:
<ng-container *ngFor="let cat of Cats">
<div *ngIf="counts[cat]">
<div class="row ">
<span class="">{{cat}}</span>
</div>
<ul class="mdc-image-list my-image-list" style="padding-left: 10px;padding-right: 10px;">
<ng-container *ngFor="let product of data; let j = index;">
<li class="mdc-image-list__item" *ngIf="product.Cat == cat">
<div class="mdc-image-list__image-aspect-container">
<ng-container *ngIf="product.image == null; else productImage">
<img src="./assets/image-not-found.svg" class="mdc-image-list__image imagenotfound">
</ng-container>
<ng-template #productImage>
<img [src]="product.image" class="mdc-image-list__image">
</ng-template>
</div>
</li>
</ng-container>
</ul>
</div>
</ng-container>
You can write format function, write data as an object where the key is category, value is array of images and use keyvalue pipe.
For example:
component property: formattedData: {[key: string]: string[]} = {}
format function:
formatData(data: {image: string; cat: string}[]): void {
data.forEach((item: {image: string; cat: string}) => {
if (this.formattedData[item.cat]) {
this.formattedData[item.cat].push(item.image)
} else {
this.formattedData[item.cat] = [item.image]
}
});
}
call the format function in constructor:
constructor() {
this.formatData(this.data);
}
template:
<div *ngFor="let item of formattedData | keyvalue">
<div class="row">
<span class="">{{item.key}}</span>
</div>
<ul class="mdc-image-list my-image-list" style="padding-left: 10px;padding-right: 10px;">
<ng-container *ngFor="let image of item.value;">
<li class="mdc-image-list__item">
<div class="mdc-image-list__image-aspect-container">
<img [src]="image ? image : './assets/image-not-found.svg'" class="mdc-image-list__image" [ngClass]="{'imagenotfound': !image}">
</div>
</li>
</ng-container>
</ul>
</div>
I think it looks better because we have a little less logic in the template. You have only listed categories that have images.
<div ng-app="appPage" ng-controller="appController">
<div class="nav">
<h1 class="logo">Todlio</h1>
<i class="icon setting" style="color:#fff;font-size:1.8em;;position:absolute;top:11px;right:12px;"/></i>
</div>
<div class="todo">
<div class="todo_column">
<div style="font-weight700;text-align:center; margin:20px;">
<a href="#/add" ng-click="addTodo()" class="ui basic button">
<i class="add square icon"></i>
Add
</a>
</div>
<ul>
<a href="#/"><li ng-href="#/" ng-click="detail($index)" ng-repeat="todo in todos">
<h3 ng-model="title">{{ todo.title }}</h3>
<h6>{{ todo.note_date }}</h6>
</li></a>
</ul>
</div>
<div class="todo_full">
<div class="todo_title" ng-scope="todo in todos">
<span><h1>{{ title }}</h1></span>
<span class="check">
<i style="font-size:2em;" class="square outline icon"></i>
<i class="write icon" style="font-size:1.8em;"></i>
</span>
</div>
<h4>Note:</h4>
<p class="todo_note">{{ note }}
</p>
</div>
</div>
</div>
Controller
app.controller("appController", function ($scope) {
$scope.todos = [
{title: "Call Vaibhav", note: "", note_date: ""},
{title: "Grocery", note: "Lemons, Apple and Coffee", note_date: ""},
{title: "Website design for Stallioners", note: "UI/UX on xyz#mail.com", note_date: ""},
{title: "Fill MCA form", note: "First search for all the colleges", note_date: "" }
];
$scope.detail = function(x){
$scope.todos.title = $scope.title;
$scope.todos.note = $scope.note;
};
I want to get the clicked list item title and the note attached to it to the different div below
Can anybody please help. Its a todo app the left half has the list to todos and the right half has a note attached to it or anything checked or not.
There are a few ways to do this. One easy way is as follows:
Define a $scope variable with a name like $scope.currentTodo.
In the repeat loop, the ng-click would set $scope.currentTodo=todo
This current variable will hold the todo object so you can use {{ $scope.currentTodo.title }} in place of {{title}}
Ditch the ng-scope
I have something like an input to add items into either column 1 or column 2 and each time adding the items, the column will show up what is added right away with an 'X' beside it so if you want to delete the item just click on 'X'. At first I wasn't thinking much so I used an easy way to remove the HTML but then I realized, that's just removing the HTML (There's also a search input if I type something into search and clear the search, all items will show again). This is when I realized just removing the HTML is a mistake that I need to remove the object too but how can I make it so it'll delete the right object in the array?
My angular script
angular.module("addItemApp", [])
.controller("toDoCtrl", function ($scope) {
$scope.items = [];
$scope.addItem = function (item) {
console.log(item);
$scope.items.push(angular.copy(item));
console.log($scope.items);
};
$scope.remove = function (item) {
var index = $scope.items.indexOf(item);
$scope.items.splice(index, 1);
}
});
my html
<div class="row">
<div class="col-xs-6 col-sm-4 left-column">
<div class="input-item">
<input type="text" placeholder="Enter Item" ng-model="item.name" class="enter-item">
<select class="column-select" ng-model="item.pos">
<option value="default" selected>Choose Column</option>
<option value="column1">Column 1</option>
<option value="column2">Column 2</option>
</select>
<button class="add-button" type="button" ng-click="addItem(item)">Add Item</button>
</div>
<div class="search-item">
<label for="search">Search An Item</label>
<div class="search-input">
<input ng-model="query" type="text" placeholder="Search" id="search"><span class="fa fa-search icon-search"></span>
</div>
</div>
</div>
<div class="col-xs-6 col-sm-4">
<h3 class="column-header column1">
Column 1
</h3>
<ul ng-repeat="item in items | filter:{ pos: 'column1' } | filter:query">
<li>{{item.name}}
<button remove-on-click ng-click="remove()" class="remove-button fa fa-times"></button>
</li>
</ul>
</div>
<!-- Optional: clear the XS cols if their content doesn't match in height -->
<div class="clearfix visible-xs-block"></div>
<div class="col-xs-6 col-sm-4">
<h3 class="column-header column2">
Column 2
</h3>
<ul ng-repeat="item in items | filter:{ pos: 'column2' } | filter:query">
<li>{{item.name}}
<button remove-on-click ng-click="remove()" class="remove-button fa fa-times"></button>
</li>
</ul>
</div>
</div>
Thanks in advance everyone.
you can do it in two ways -
1
$scope.remove = function(item) {
var index = $scope.items.indexOf(item);
$scope.items.splice(index, 1);
}
<button ng-click="remove(item)"></button>
2
$scope.remove = function(index) {
$scope.items.splice(index, 1);
}
<button ng-click="remove($index)"></button>
Please note that, when filter is applied, the $index may not be the one you should use to remove, better go with first approach. I have given example of $index for your reference.
<button ng-click="remove(item)"></button>
should work, since item is defined earlier in the ng-repeat and you already have a remove function defined on your $scope.
I have two tabs and have to implement filtering on both of them.
In second tab everything works fine, but in first one not.
I'm filling that problem relates to this part(ng-model):
<ul search-list=".letter" model="search.district">
<li class="letter" ng-repeat="letter in alphabet.letter">{{ letter }}</li>
</ul>
and to this:
<input id="q" type="text" ng-model="search.district " />
So second tab works fine:
<div ng-repeat="district in districts | filter:search:startsWith" class="district">
<h4 class="district-name">{{ district.district }}</h4>
Some info
<ul class="district-cities">
<li ng-repeat="city in district.cities ">
<a href="{{ city.url }}">
{{ city.name }}
</a>
</li>
</ul>
</div>
But how to make works first part I don't know...
Here is DEMO
Do this changes in HTML
<div class="tab-content active" data-id="first">
<li ng-repeat='city in cities | filter:{name :search.district}:startsWith'>
<a href="{{ city.link }}">
{{ city.name }}
</a>
</li>
</div>
Here is the updatd Plunker
In your startsWith method you are specifically referencing the district property of the object. This property doesn't exist on city objects:
$scope.startsWith = function (actual, expected) {
var lowerStr = (actual + "").toLowerCase();
var e = angular.isString(expected) ? expected.toLowerCase() :
// district is not present in City object
expected.district.toLowerCase();
return lowerStr.indexOf(e) === 0;
}
Since you're using search.district, it tries to access the .district value for the cities.
You can use search.name instead, here...
<ul search-list=".letter" model="search.name">
...and here...
<input id="q" type="text" ng-model="search.name" />
and then rename district to name on each district of the district array.
I'm having three different sets of drop downs and also search box to apply filter for a table. My table should filter if someone enters text in textbox (or) depending on the value selected in one of the three drop downs (textbox searches and drop down searches are exclusive of each other).
This is my HTML:
<div ng-app="myModule" ng-controller="myController">
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<form class="navbar-form navbar-left form-horizontal" role="search">
<div class="form-group">
<label for="searchSeries" class="control-label">Find:</label>
<input type="text" id="searchSeries" ng-model="searchText" class="form-control" placeholder="Search TV Series" />
<div class="btn-group btn-input clearfix">
<button type="button" class="btn btn-default dropdown-toggle form-control" data-toggle="dropdown"> <span data-bind="label">Select One</span> <span class="caret"></span> </button>
<ul class="dropdown-menu" role="menu">
<li>Action</li>
<li>Animation</li>
<li>Comedy</li>
<li>Fantasy</li>
<li>Drama</li>
<li>Mystery</li>
<li>Romance</li>
<li>Science Fiction</li>
<li>Mini Series</li>
<li>Reality</li>
<li>Documentary</li>
<li>Crime</li>
<li>Game Show</li>
<li>Talk Show</li>
<li>Adventure</li>
<li>Home and Garden</li>
<li>Thriller</li>
<li>Sport</li>
<li>Family</li>
<li>Children</li>
<li>News</li>
<li>Horror</li>
</ul>
</div>
<select ng-model="properties.config" ng-options="prop.value as prop.name for prop in properties.configs">
</select>
<select ng-model="order.config" ng-options="template.value as template.name for template in order.configs">
</select>
</div>
</form>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container-fluid -->
</nav>
<div class="container-fluid">
<div class="table-responsive">
<table class="table table-bordered">
<tbody>
<tr ng-repeat="user in users[0].tvseries | filter: searchText | filter: order.config | filter:properties.config">
<td><img ng-src="{{user.thumbnail}}" alt="" /></td>
<td><div>{{user.tv_show_name}}</div>
<div>{{user.brief_description}}</div>
<div>{{user.rating}}</div></td>
<td><div>{{user.show_time}}</div>
<div>{{user.genre}}</div>
<div>{{user.current_season}}</div>
<div>{{user.current_episode}}</div></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
My controllers.js:
var myModule = angular.module('myModule', []);
myModule.controller('myController', function($scope, userRepository) {
userRepository.getAllUsers().success(function(users) {$scope.users = users;});
$scope.order = {};
$scope.properties = {};
//Configuration
$scope.order.configs = [
{'name': 'Ascending',
'value': 'tv_show_name'},
{'name': 'Descending',
'value': '-tv_show_name'}
];
$scope.properties.configs = [
{'name': 'Show Time',
'value': 'show_time'},
{'name': 'Rating',
'value': 'rating'}
];
//Setting first option as selected in configuration select
$scope.order.config = $scope.order.configs[0].value;
$scope.properties.config = $scope.properties.configs[0].value;
});
myModule.factory('userRepository', function($http) {
return {
getAllUsers: function() {
var url = "https://api.mongolab.com/api/1/databases/tvshowsdb/collections/tvshowsdbcollections?apiKey=e2SbmkVmLOQN-wH_ga84n9prYsgU8lQ5";
return $http.get(url);
}
};
});
Note that: when I apply "filter: searchText" in my html, it works fine and I face no issue. But when I try to apply two more filters "filter: order.config | filter:properties.config", my code doesn't execute. Please help me understand where I'm going wrong :(
Because you are adding and removing - before tv_show_name, I think that you are trying to sort the results by multiple fields? If that is the case, the syntax would be:
orderBy: [order.config, properties.config].
Here is a demo: http://plnkr.co/edit/yLYQGYGcGZMqoj6yfqoT?p=preview
Note: the demo is reading from a json file, instead of the mongolab api, because I needed to add duplicate names and start times, to show the effects of multi-level sorting. Also, notice that I changed the order of the selects to match the order in which you were applying the filters. I think it is intuitive that the first select take precedence over the next select.
Update
If you only want to sort by one of the options at a time, you can use a string variable, much like the orderBy api demo:
<div ng-click="predicate = 'tv_show_name'">Show Name ascending</div>
<div ng-click="predicate = '-tv_show_name'">Show Name descending</div>
<div ng-click="predicate = 'show_time'">Show Time</div>
<div ng-click="predicate = 'rating'">Rating</div>
...
<tr ng-repeat="user in users[0].tvseries | filter: searchText | orderBy:predicate">
Here is the updated Demo