Ionic + AngularJS: ng-repeat filter not working on grid layout but works in list - javascript

I am currently learning building apps with ionic v1 and angular.
Here's the scenario, I'm trying to make a grid of cards and was able to do it by following the tutorial on this link.
I was able to populate the grid with cards using json data. However, I also need to add a search box in order to filter the results. I used angularJS ng-repeat filter on the view but it is not filtering it correctly.
here's my code:
angular.module('starter')
.factory('Disasters', function() {
var disasters= [{
id: 0,
name: 'Earthquake',
face: 'img/earthquake/thumbs.PNG',
}, {
id: 1,
name: 'Flash Floods',
face: 'img/flood/thumbs.png',
}, {
id: 2,
name: 'Typhoon',
face: 'img/typhoon/thumbs.png',
}, {
id: 3,
name: 'Avalanche',
face: 'img/avalanche/thumbs.png',
}];
return {
all: function() {
return disasters;
},
remove: function(disaster) {
disasters.splice(disasters.indexOf(disaster), 1);
},
get: function(disasterID) {
for (var i = 0; i < disasters.length; i++) {
if (disasters[i].id === parseInt(disasterID)) {
return disasters[i];
}
}
return null;
}
};
})
.controller('DisasterCtrl', function($scope,$state,$ionicSideMenuDelegate,Chats,Facts){
$scope.disasters = Disasters.all();
})
and here's the code for my view
<ion-content>
<input type="text" ng-model="search">
<div ng-repeat="disaster in disasters | filter: search" ng-if="$index % 2 === 0">
<div class="row">
<div class="col col-50 no-padding" ng-if="$index < disasters.length">
<div class="list card card-cell">
<div class="item item-image background-image" back-img="{{disasters[$index].slider[0].image}}">
</div>
<div class="item item-avatar" ng-click="href({{disasters[$index].id}})">
<p class="bold uppercase font12">{{disasters[$index].name}}</p>
</div>
</div>
</div>
<div class="col col-50 no-padding" ng-if="$index < disasters.length">
<div class="list card card-cell">
<div class="item item-image background-image" back-img="{{disasters[$index + 1].slider[0].image}}">
</div>
<div class="item item-avatar" ng-click="href({{disasters[$index + 1].id}})">
<p class="bold uppercase font12">{{disasters[$index + 1].name}}</p>
</div>
</div>
</div>
</div>
</div>
</ion-content>
when I type something on the search text box it always shows the first two object in the JSON data.
If I change the view into a list the filter works, however it is required to apply the grid styling on the view.
Thanks!
Pluker link of the code : plnk code link

The problem with your grid layouting is that you are using the ng-repeat in a row instead of the columns so when the search item is among a particular it returns the full row because thats where your item is located. so i created a code to help you sort it in plnk where i used the ng-repeat in a column instead of the row and added a style to the row to make it break which is this
class="row" style="flex-wrap: wrap;"
Input: <input type="text" ng-model="search.name" style="border:1px solid #ccc">
<br>
<div class="row" style="flex-wrap: wrap;" >
<div class="col col-50 no-padding" ng-repeat="disaster in disasters | filter: search" >
{{$index}}
<div class="list card card-cell">
<div class="item item-image background-image" back-img="{{disaster.slider[0].image}}">
</div>
<div class="item item-avatar" ng-click="href({{disaster.id}})">
<p class="bold uppercase font12">{{disaster.name}}</p>
</div>
</div>
</div>
</div>
That should work for you as needed. here is the demo running demo
reference from this answer

Related

Attach and Remove a CSS class using ngClass in Angular JS

I am building an app using Angular JS. I am using ng-repeat to loop over list of items. My code is something like below,
<div class="names" ng-repeat="room in rooms" ng-click="getMessages(room.id); active = true; " ng-class="{'blue_act': active}">
<div class="con_cir col-lg-2 col-md-2 col-sm-2">
<div class="grey_cir">
<div class="wht_cir">
<div class="circle1">
<div class="cir_txt">{{room.title | nameInitials}}</div>
</div>
</div>
</div>
</div>
<div class="em_info col-lg-10 col-md-10 col-sm-10">
<div class="em_pr">
<div class="name boldfnt col-lg-7 col-md-7 col-sm-7" ng-class="{'normal-font': active}">
{{room.title}}
</div>
<div class="date col-lg-5 col-md-5 col-sm-5">
{{room.lastActivity | date: 'd/M/yyyy'}}
</div>
</div>
<div class="subject boldfnt col-lg-12 col-md-12 col-sm-12">
Here is your copy of your coupans.
</div>
</div>
</div>
Now I want to apply the class blue_act only when the particular row is selected. I don't want to write any function in the controller to achieve this.
For example When I click the Demo#16 only that row will have the blue background. And when I click the e2Spark only that row will be highlighted using the blue color. Is it possible with only ng-click and ng-class?
Screenshot
Create a new property on controller's scope, called 'selectedRow', and assign room name to it on click.
plnkr
var app = angular.module('plunker', []);
app.controller('MainCtrl', function() {
this.rooms = [
{name: 'room1'},
{name: 'room2'},
{name: 'room3'},
];
this.selectedRow = this.rooms[0].name;
});
.blue_act {
background-color: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<body ng-app="plunker" ng-controller="MainCtrl as main">
selected row: {{main.selectedRow}}
<div class="names" ng-repeat="room in main.rooms" ng-click="main.selectedRow = room.name" ng-class="{'blue_act': main.selectedRow == room.name}">
<div class="con_cir col-lg-2 col-md-2 col-sm-2">
<div class="grey_cir">
<div class="wht_cir">
<div class="circle1">
<div class="cir_txt">{{room.name }}</div>
</div>
</div>
</div>
</div>
</div>
</body>
Replace your click event with this ng-click="getMessages(room.id); reset(); room.active = true; "
and do same for ng-class where you are using as like ng-class="{'normal-font': room.active}"
Add below reset function in your code if you want select at a time single row.
$scope.reset= function(){
angular.forEach($scope.rooms, function(room) {
room.active=false;
});
}
in your ng-click
ng-click="getMessages(room.id); room.active = !room.active;"
in your ng-class
ng-class="{'normal-font': room.active }"
With no controller function you could use
<div class="names" ng-repeat="room in rooms" ng-click="getMessages(room.id);room.active=!room.active;" ng-class="{'blue_act': room.active}">
...
</div>
Or if you just wanted to have one active at the same time this should work:
<div class="names" ng-repeat="room in rooms" ng-click="getMessages(room.id);selectedRoom=room.id;" ng-class="{'blue_act': selectedRoom===room.id}">
...
</div>

how to use ng-show to display specific divs inside an ng-repeat?

I have an ng-repeat div to dynamically display a set of div tags based on a dynamic list of items, that each have a unique Id , a type and a "title" value which are both strings and I have a click function assigned to each of them. When I want to click on one of these divs , I want to display an separate div associated to that clicked div and I want to do that using an ng-show which at the moment has a condition where the id of this item/div should be equal/equivalent to a
scope variable I've defined in a controller associated with the html for this new div to be displayed.
The problem I'm getting is that every one of these separate divs is showing and assuming that all the ng-shows are true which shouldn't be the case and I'm not sure why this is happening as all the id's for the items are unique. I've printed out to the console and can't see anything wrong with the assigning of the variable but not sure if I've missed something with regards to the ng-show condition.
Here is what I have so far in html with 2 div examples (don't worry about replicating all the styling, i just want to figure out what is going on with the html/angular stuff):
<div class="col-md-12 col-sm-12 col-xs-12 " ng-repeat="item in items track by item.id" ng-click="onClick(item.id)">
//first type div that is clickable
<div class="row">
<div>
<header class="text">
<h1 data-ng-if="item.title" ng-bind-html="item.title"></h1>
</header>
</div>
</div>
//div to be displayed after associated first div type is clicked
<div class=" col-sm-11 row" ng-show="displayMessage===item.id" >
<header class="col-sm-12 text">
<h1 data-ng-if="item.title" ng-bind-html="item.title"></h1>
</header>
<p class="col-sm-12" > text about {{item.id]} </p>
</div>
//2nd type of div
<div class="row" style=" background: linear-gradient(135deg, #156570, #1e9d8b);">
<h1 data-ng-if="item.title" ng-bind-html="item.title"></h1>
<i class="ms-icon ms-icon-heart-rate"></i>
<div class="clearfix"></div>
</div>
//div to be displayed after associated second div is clicked
<div class="col-md-11 col-sm-11 col-xs-11" ng-show="displayMessage===item.id">
<div style="background: linear-gradient(135deg, #156570, #1e9d8b);"></div>
<h1 class="col-sm-12 col-xs-12" data-ng-if="item.title" ng-bind-html="item.title" style="color:#000"></h1>
<p class="col-sm-12 col-xs-12 "> text associated with {{item.id}}
</p>
</div>
</div>
And here is the simple click function I have . $scope.displayMessage is defined earlier on during the controller setup where its set to an empty string:
$scope.onClick = function (itemId) {
$scope.displayMessage = itemId;
}
Let me know if I need to add more code.
It could be done in a simpler way without adding property to the items varible you have in scope.
Let the Controller be
app.controller('MainCtrl', function($scope) {
$scope.div_=[];
$scope.items = [
{
id: 1,
title: "first item"
},
{
id: 2,
title: "second item",
},
{
id: 3,
title: "third item",
}
];
$scope.onClick=function(index,row){
$scope.div_[index+'_'+row]=true;
}
});
HTML will be like
<body ng-controller="MainCtrl">
<div ng-repeat="item in items">
<div style="color:red" ng-click="onClick($index,0)">DIV {{$index}}.0---click to show DIV {{$index}}.0_CHILD</div>
<div style="color:blue" ng-show="div_[$index+'_0']">DIV {{$index}}.0_CLILD</div>
<br>
<div style="color:red" ng-click="onClick($index,1)">DIV {{$index}}.1---click to show DIV {{$index}}.1_CHILD</div>
<div style="color:blue" ng-show="div_[$index+'_1']">DIV {{$index}}.1_CLILD</div>
<hr>
</div>
</body>
Here an array named 'div_' is maintained to trace all ng-show value of all div.
Working plunker https://plnkr.co/edit/uhFdCXkmS4gB95c9bjTR?p=preview
This will be easier for you to accomplish if you had properties on each item to key off of.
For example,
$scope.items = [
{
id: 1,
title: "first item",
isFirstDivSelected: false,
isSecondDivSelected: false
},
{
id: 2,
title: "second item",
isFirstDivSelected: false,
isSecondDivSelected: false
}
{
id: 3,
title: "third item",
isFirstDivSelected: false,
isSecondDivSelected: false
}
];
This will allow you to add an ng-click to your children items.
<div class="col-md-12 col-sm-12 col-xs-12 " ng-repeat="item in items track by item.id" ng-click="onClick(item.id)">
//first type div that is clickable
<div class="row" ng-click="item.isFirstDivSelected = true;">
<div>
<header class="text">
<h1 data-ng-if="item.title" ng-bind-html="item.title"></h1>
</header>
</div>
</div>
//div to be displayed after associated first div is clicked
<div class=" col-sm-11 row" ng-show="item.isFirstDivSelected" >
<header class="col-sm-12 text">
<h1 data-ng-if="item.title" ng-bind-html="item.title"></h1>
</header>
<p class="col-sm-12" > text about {{item.id]} </p>
</div>
//more divs.........
Right now, there is no good way to for your application to know that any of your children divs have been clicked. There are other ways you can do this, but in my opinion, adding properties that are well defined and straightforward is the best way.
if you want to show hide rows under loop
<div *ngFor="let client_obj of dashboard_data.data.items ;let i = index" >
<input type="checkbox" [(ngModel)]="div_['level_1_'+i]">
<div [class.hide]="div_['level_1_'+i]" >
{{client_obj.value}}
</div>
</div>

Bootstrap different col-* with ng-repeat

I've got a movie reviews example application built in angular + bootstrap, I'm trying to build a "grid" of 3 images for medium and up screens, the problem is that when I want to have only 2 images per row for xs screens.
I'm using ng-repeat and populating the imgs inside the cols.
is there a nice way to do this? (I'm aware that the $index + N may be out of bounds, and that there is code duplication here. just want to find a good solution for rearranging the bootstrap grid for different screen sizes on dynamic data)
<div ng-repeat="movie in movies" ng-if="$index % 3 == 0" class="row slide-left">
<div class="col-md-4">
<img ng-src='http://image.tmdb.org/t/p/w185/{{movies[$index].poster_path}}' ng-click="chooseMovie(movies[$index])">
</div>
<div class="col-md-4">
<img ng-src="http://image.tmdb.org/t/p/w185/{{movies[$index + 1].poster_path}}" ng-click="chooseMovie(movies[$index + 1])">
</div>
<div class="col-md-4">
<img ng-src="http://image.tmdb.org/t/p/w185/{{movies[$index + 2].poster_path}}" ng-click="chooseMovie(movies[$index + 2])">
</div>
</div>
<div ng-repeat="movie in movies" >
<div class="col-md-4 col-xs-6">
<img ng-src='http://image.tmdb.org/t/p/w185/{{movie.poster_path}}' ng-click="chooseMovie(movie)">
</div>
</div>
This should do the trick.
I removed the row but i think even with it will works unless you use the clearfix class.
The grid will automatically go on the next line after "12" columns.
So a size of 4 for medium screen means 3 per line, 6 for xs screen ->2/line :)
You can detect bootstrap columns by using the following function:
function findBootstrapEnvironment() {
var envs = ["ExtraSmall", "Small", "Medium", "Large"];
var envValues = ["xs", "sm", "md", "lg"];
var $el = $('<div>');
$el.appendTo($('body'));
for (var i = envValues.length - 1; i >= 0; i--) {
var envVal = envValues[i];
$el.addClass('hidden-'+envVal);
if ($el.is(':hidden')) {
$el.remove();
return envs[i]
}
};
}
The setting a value to the scope based on the return
if(findBootstrapEnvironment()==="Medium"|(findBootstrapEnvironment()==="Large"|){
$scope.size="Medium"
}else{
$scope.size="Small"
}
And then with ng-if you can manage the div to be with 3 or 2 photos like this
Three photos if medium
<div ng-repeat="movie in movies" ng-if="size==medium" class="row slide-left">
<div class="col-md-4">
<img ng-src='http://image.tmdb.org/t/p/w185/{{movies[$index].poster_path}}' ng-click="chooseMovie(movies[$index])">
</div>
<div class="col-md-4">
<img ng-src="http://image.tmdb.org/t/p/w185/{{movies[$index + 1].poster_path}}" ng-click="chooseMovie(movies[$index + 1])">
</div>
<div class="col-md-4">
<img ng-src="http://image.tmdb.org/t/p/w185/{{movies[$index + 2].poster_path}}" ng-click="chooseMovie(movies[$index + 2])">
</div>
</div>
Two photos if small
<div ng-repeat="movie in movies" ng-if="size==small" class="row slide-left">
<div class="col-sm-4">
<img ng-src='http://image.tmdb.org/t/p/w185/{{movies[$index].poster_path}}' ng-click="chooseMovie(movies[$index])">
</div>
<div class="col-sm-4">
<img ng-src="http://image.tmdb.org/t/p/w185/{{movies[$index + 1].poster_path}}" ng-click="chooseMovie(movies[$index + 1])">
</div>
</div>

ng-class to be only added if <a> exists after given element

I have a list of items that are grouped by labels, and am using filter to search through this list. The issue is that labels for lists are still showing in the search, however lists themselves are empty, hence I need to hide appropriate label. To do so I can check if anchor tag exists after my label with class .item, if it doesn't then I want to add .hidden class to the label.
I tried following, but as I am new to angular it obviously didn't work:
<div class="label" ng-class="next(a).length <= 0 ? 'hidden'">
My Label
</div>
And this is how whole group/section looks if there are items in the list:
<div class="list search-list">
<div class="label" ng-class="next(a).length <= 0 ? 'hidden'">
My Label
</div>
<a class="item> .. </a>
<!-- More a tags here -->
<div class="label" ng-class="next(a).length <= 0 ? 'hidden'">
My Label 2
</div>
<a class="item> .. </a>
<!-- More a tags here -->
</div>
I checked with inspector, relevant a tags are indeed being removed if they don't match search.
UPDATE:
added false case to ng-class, but still no luck
<div class="label" ng-class="next(a).length <= 0 ? 'hidden' : 'shown'">
My Label
</div>
UPDATE2: Full structure
<ion-view view-title="Search">
<ion-content ng-controller="SearchCtrl">
<div class="list search-bar">
<label class="item item-input">
<i class="icon ion-ios-search-strong placeholder-icon"></i>
<input type="text" placeholder="What are you looking for?" ng-model="searchFilter">
</label>
</div>
<div class="list search-list">
<!-- Group 1 -->
<div class="item item-divider" ng-class="angular.next('a').length <= 0 ? 'hidden' : 'shown'">
My Title
</div>
<a
class="item item-avatar"
href="#/app/{{item.link}}"
ng-repeat="item in items | filter:searchFilter">
<img src="img/item.png">
<h2>{{item.title}}</h2>
<p>Description</p>
</a>
<!-- Group 2 -->
<div class="item item-divider" ng-class="angular.next('a').length <= 0 ? 'hidden' : 'shown'">
My Title 2
</div>
<a
class="item item-avatar"
href="#/app/{{item2.link}}"
ng-repeat="item2 in items2 | filter:searchFilter">
<img src="img/item2.png">
<h2>{{item.title}}</h2>
<p>Description 2</p>
</a>
</div>
</ion-content>
</ion-view>
In case you want to follow the original approach of checking if there is <a/> element after your <div> you can write a custom directive. Here is one that checks for that condition:
.directive('conditionalDisplay', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
if (element.next('a.item').length == 0)
element.css('display', 'none');
}
}
});
And here is how I used it:
<div class="list search-list">
<div conditional-display class="label">
My Label (Not Displayed, because there is no next a element)
</div>
<!--<a class="item"> .. </a>-->
<!-- More a tags here -->
<div conditional-display class="label">
My Label 2 (Displayed)
</div>
<a class="item"></a>
<!-- More a tags here -->
</div>
Here is a working plunker: http://plnkr.co/edit/ytCVxuBnbZAbX0faiQ4m?p=preview
One important thing: Make sure you include jquery for the next() selector to work. JQLite does not support next() with selectors.
Personally, I like #pgreen2 's approach. It more like angular way of doing stuff.
As described by ng-init, you could store the results of the filter and then reference like below:
<!-- Group 1 -->
<div ng-init="filteredItems = (items | filter:searchFilter)" ng-if="filteredItems">
<div class="item item-divider">
My Title
</div>
<a class="item item-avatar" href="#/app/{{item.link}}" ng-repeat="item in filteredItems">
<img src="img/item.png">
<h2>{{item.title}}</h2>
<p>Description</p>
</a>
</div>
I wrapped the whole group in a div that has ng-init which creates a variable called filteredItems that us it two other places. The first is to conditinally render the entire div using ng-if. I believe this will resolve your actual question. Secondly, I use filteredItems in the ng-repeat for the anchor tags.

AngularJS Loops and Formatting

very new to AngularJS so please forgive me if this is a stupid question.
I need to output my data in a grid like format (using Ionic) and i need to have a div for a row and separate divs for columns, like this
<div class="row">
<div class="col-33">Item 1</div>
<div class="col-33">Item 2</div>
<div class="col-33">Item 3</div>
</div>
<div class="row">
<div class="col-33">Item 4</div>
<div class="col-33">Item 5</div>
<div class="col-33">Item 6</div>
</div>
My data is within a list, much like this.
$scope.images = [];
$scope.images.push({text: 1});
$scope.images.push({text: 2});
$scope.images.push({text: 3});
$scope.images.push({text: 4});
$scope.images.push({text: 5});
$scope.images.push({text: 6});
How can i use the $scope.images list to output my as a grid?
The closest i have got is below, but it doesnt work.
<ion-content>
<div class="list list-inset" ng-init="myIndex = 0">
{{myIndex }} : {{ images.length }}
<div ng-repeat="myIndex < images.length" >
<div class="row" ng-repeat="colIndex in [0, 1, 2]" ng-init="colIndex = 0">
<div ng-show="myIndex + colIndex < images.length" class="col-33" ng-init="myIndex = myIndex + 1">
{{ myIndex }}:{{ colIndex }}:{{myIndex + colIndex}}:{{ images[myIndex + colIndex].text}}
</div>
</div>
</div>
</div>
</ion-content>
Is there such a thing as a while loop? I was hoping i could increase the $index variable if i used ng-repeat="item in images", but not sure how to do that either.
Thanks in advance
Something like this? fiddle
<span style="float: left">{{item}}</span><br ng-if="($index+1)%3 == 0"/>
I simply break the line every three items, but we could expand on this approach.
Update with complete, working solution: fiddle
<div class="container">
<div ng-repeat="item in items" ng-if="$index%3==0" class="row">
<span ng-if="$index<items.length" style="float: left">{{items[$index]}}</span>
<span ng-if="($index+1)<items.length" style="float: left">{{items[$index+1]}}</span>
<span ng-if="($index+2)<items.length" style="float: left">{{items[$index+2]}}</span>
</div>
</div>
The code is pretty self-explaining: the solution creates a row every three elements, and inserts the elements only if they actually exist.
<div class="row" ng-repeat="photo in photos" ng-if="$index % 3 == 0" ng-init="photoIndex = $index">
<div ng-repeat="i in [0,1,2]" ng-if="(photoIndex + i)<photos.length" class="col-33">
<img ng-src="{{photos[photoIndex+i]}}">
</div>
</div>
Here is a more compact version of the above answer.

Categories

Resources