AngularJS: apply class to multiple elements in array on click - angularjs

I'm teaching myself angular and working through the O'Reilly AngularJS book. There's an email app example and I want to add a button "Mark as Read" that will apply a class to the row when the button is clicked.
After doing some research I found solutions applying conditional classes based on using $index to select a row, but that is toggling the class from the other elements when I click the button. You can see what I'm talking about here in this Plunker.
Here's the HTML in my view:
<table>
<tr>
<td><strong>Sender</strong></td>
<td><strong>Subject</strong></td>
<td><strong>Date</strong></td>
</tr>
<tr ng-repeat='message in messages' ng-click='readMessage($index)'
ng-class='{markRead: $index==selectedRow}'>
<td>{{message.sender}}</td>
<td><a ng-href='#/view/{{message.id}}'>{{message.subject}}</a></td>
<td>{{message.date}}</td>
<td><button ng-click="remove(message)">Delete</button></td>
<td><button ng-click="markRead()">Mark as Read</button></td>
</tr>
</table>
and here is what I have added to the controller to apply the class on click:
$scope.readMessage = function(row) {
$scope.selectedRow = row;
};
My goal is to have the ability to apply the class to any and all elements in the array, as the user desires (not just one at a time).
Can someone please help me understand why this isn't working and how I can achieve my goal? All the questions/answers I can find on SO are about toggling classes or setting an active class, which is the opposite of what I'm trying to accomplish.
Thank you!

The function readMessage could flag the message as read, and ng-class set the css class if the flag is set:
Controller:
$scope.readMessage = function(message) {
message.read = true;
};
Template:
ng-class='{markRead: message.read}'
See plunker

Related

Hide table rows in angular Js

I have a table, I am already given it CSS using ng-class if they satisfy a condition. Now I want to show only those rows who satisfy the same condition on a button click. I have wrote a controller which checks if the data received is within 24 hours are not and marks the data cell. Until this it's working.Now I need to add a button and show only the row which has this td marked as not received in time.
<tbody>
<tr ng-repeat ="data in log">
<td>{{data.UniqueId}}</td>
<td>{{data.Name}}</td>
<td ng-class ="{'data-notreceived' : dataNotReceived('data.receivedTime')}">{{data.receivedTime
}}
</tbody>
</table>
I think something like this should work. Basically, clicking the button will toggle between showing all or only the items marked as 'data not received'.
<tbody>
<tr ng-repeat ="data in log" ng-show="showAll || dataNotReceived(data.receivedTime)">
<td>{{data.UniqueId}}</td>
<td>{{data.Name}}</td>
<td ng-class ="{'data-notreceived' : dataNotReceived('data.receivedTime')}">{{data.receivedTime}}
</tr>
</tbody>
// in controller
$scope.showAll = true;
$scope.onButtonClick = function() {
$scope.showAll = !$scope.showAll;
return false;
}
From the information provided in question what I can say is: Use ng-show to show rows based on your condition.
<tr ng-show ="your_condition">
You could also use an ng-if rather than ng-show. See the differences here.
Really depends on how often the hide/show toggle needs to happen.
<tbody>
<tr ng-repeat="data in log" ng-if="showLast24Hrs(data.ReceivedTime)">
<td>{{data.UniqueId}}</td>
<td>{{data.Name}}</td>
<td>{{data.ReceivedTime}}</td>
</tbody>
and then in the controller,
$scope.showLast24Hrs = function(receivedTime){
if($scope.isLast24Hours) {
return receivedTime < 200; // Write your less than 24 hours check here
}
return true;
}
I wrote this demo on Codepen. Hope that helps.

Make a single button active in a group of buttons created by a ng-repeat

I am working on a directive that displays a table using ng-repeat directives. Each cell in the table may contain a button depending on the data. Here is the pertinent snippet of the template.
<tbody>
<tr ng-repeat="row in data.rows">
<td>{{ row.rowName }}</td>
<td ng-repeat="cell in row.cells">
<button id="button-{{ row.id }}-{{ cell.id }}"
class="btn btn-primary"
ng-show="cell.isActive"
ng-click="onClick(row.id, cell.id, $event)">Select</button>
</td>
</tr>
</tbody>
In the controller I have
$scope.onClick = function (rowId, cellId, event) {
$scope.selectedRowId = rowId;
$scope.selectedCellId = cellId;
$scope.selectedButtonId = event.target.id;
};
The goal is to make it such that when a user clicks a button in the table, that button becomes active. When the user clicks on another button, it will become active making the previously active button inactive. This functionality will be used to drive what is displayed on another section of the page.
I have tried a few things, but I think the most angular way of doing things is to use ng-class in the buttons to apply the active and inactive classes based on what button has been clicked. To this end, I am assigning them all unique ids and tracking which button has been clicked based on the event.
The problem is I can not figure out how to make the ng-class work. Is there some way I can get the id of the current button that I am in, or am I going about this completely wrong in the first place.
Thanks.
In your button code set the html ng-class attribute :
ng-class="active : selectedRowId = row.id & selectedCellId = cell.id"

Angular UI-Router ui-sref ignore some elements

I have an interesting problem with the uiSref directive and I haven't been able to find a solution (well an elegant one anyway) anywhere on the web. Basically I have a requirement from a client to be able to click a row in a table of resources and go to the editing view for that resource. Normally the uiSref directive works beautifully, but the problem resides in the fact that I have a Bootstrap dropdown in the last <td> of the table with a bunch of quick actions in it. The HTML looks something like this:
<table class="table table-bordedered table-hover">
<thead>
<tr>
<td>Name</td>
<td>Actions</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="resource in resources" ui-sref="edit({id: resource.id})">
<td ng-bind="resource.name"></td>
<td class="actions-column">
<div class="btn btn-xs btn-default" data-toggle="dropdown">
<i class="fa fa-cog"></i>
</div>
<ul class="dropdown-menu pull-right">
<li>
SOMETHING CRAZY
</li>
</ul>
</td>
</tr>
</tbody>
</table>
The problem is that when I click on the button in the actions column, the uiSref overrides the default action of the dropdown and takes me to the edit page. Now you might be asking yourself "well that's easy, why can't you just stop the propagation of the event!?"... doesn't work. When I add this to the actions column:
<td class="actions-column" ng-click="$event.stopPropagation()">
It kills the functionality of the dropdown menu and nothing shows up. Right now I have a workaround in place where I define an ngClick on the <tr> element that then deciphers where the state should go depending on the element clicked like so:
<tr ng-repeat="resource in resources" ng-click="goToEdit(resource, $event)">
And The JS looks like this:
scope.goToEdit = function(resource, event) {
// if the event.target has parent '.actions-column' or is that column, do nothing else
// invoke $state.go('edit', {id: resource.id})
}
I hate it though and I have a lot of list views like this. All I'm looking for is an elegant and portable solution that hopefully works natively through UI Router like $event.stopPropagation() (Although I've poked through the UI Router source and can't seem to find a workable alternative). Basically I want to have my cake and eat it too. Anyway, it'll be interesting to see what the SO community can come up with or if what I'm asking for is not currently possible. Thanks!
I got it! While looking through the UI Router source some more, it appears that the click event will be ignored if the target attribute is populated on the element that the uiSref resides on. It may not be the most beautiful thing in the world, but it sure is easier than what I was doing before.
NOTE: This only works if you're using the whole jQuery library, not jQLite
So I wrote this directive:
app.directive('uiSrefIgnore', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.on('click', function(e) {
// Find the ui sref parent
var uiSref = elem.parents('[ui-sref]').first();
// Set the target attribute so that the click event is ignored
uiSref.attr({
target: 'true'
});
// Function to remove the target attribute pushed to the bottom
// of the event loop. This allows for a digest cycle to be run
// and the uiSref element will be evaluated while the attribute
// is populated
setTimeout(function() {
uiSref.attr({
target: null
});
}, 0);
});
}
};
});
That way, whenever I want to ignore the javascript event for just the uiSref directive, I can just add this to the html:
<tr ui-sref="edit">
<!-- any other elements -->
<td ui-sref-ignore>The element I care about</td>
</tr>
BOOM! Let me know what you guys think about the implications of this.

How do I make an entire row clickable except for one cell and its margins in angular?

Let's say I have a row that looks like
<tr>
<td>John</td>
<td>Smith</td>
<td>Approved?<input type="checkbox"/></td>
</tr>
Each row shows one employee, and allows you to check to approve/disapprove the employee (e.g. for registration for a course). I want the user to be able to click anywhere on the row to get greater detail about the employee, but if they click on the last column ("Approved?") it should not go to greater detail, since it should just change the checkbox.
Here are the solutions I know of, none is great:
Entire row: <tr class="clickable" ng-click="go()">. Makes all the cells and margins clickable, and only requires one ng-click entry, but then the checkbox causes "go()" to execute, which is bad.
Each cell: <td class="clickable" ng-click="go()">...<td class="clickable" ng-click="go()">. Pro: can restrict to just the cells I want. Con: lots of repetition (not DRY), and misses the margins.
Entire row with special "go" fn: <tr class="clickable" ng-click="go()">, but "go" knows how to differentiate between different cells. Pro: Has exactly the effect. Con: requires lots of view/html knowledge in a specialized controller action.
How can I make the first 2 columns and their margins clickable, but not the 3rd or its margins?
You can use two click handlers, one for a whole tr , second for a last td' checkbox. In the second use Event's stopPropagation method.
Controller:
var TableCtrl = function($scope){
$scope.click1 = function(){
console.log("Click 1 method")
}
$scope.click2 = function(e){
console.log("Click 2 method");
e.stopPropagation();
}
}
Markup:
<table ng-controller="TableCtrl">
<tr ng-click="click1()">
<td>John</td> <td>Smith</td>
<td>Approved?<input type="checkbox" ng-click="click2($event)"/></td>
</tr>
</table>
Working example: http://jsfiddle.net/zDMQB/1/
How about this?
<tr>
<td style="cursor: pointer;" ng-click="alert('hello1')">John</td>
<td style="cursor: pointer;" ng-click="alert('hello2')">Smith</td>
<td>Approved?<input type="checkbox"/></td>
</tr>

Can't invoke action on the button inside ng-repeat directive

I'm new to AngularJS, and while playing with it, i have encountered with the problem.... I have a table like this :
...
<tr ng-repeat="line in lines">
<remove-line>
<input id="line_id" type="hidden" value="{{line.id}}">
<td id="sn">{{$index+1}}</td>
<td>{{line.ref}}</td>
<td>{{line.label}}</td>
<td class="tva">{{line.tva}}</td>
<td class="qty">{{line.qty}}</td>
<td class="unity">{{line.unity}}</td>
<td class="prix">{{line.prix}}</td>
<td>{{line.prix*line.qty}}</td>
<td><button class="btn" id= "remove"><i class="icon-remove"></i> </button></td>
</remove-line>
</tr>
...
I want to have some behavior when clicking on the remove button, using custom directive. AngularJS code looks like :
angular.module('myApp', []).directive('removeLine',function(){
var remove = function(){
...
alert ("Oops removed!");
}
return {
restrict: 'E',
link : function(scope,element, attrs){
$("#remove").on('click', remove);
}
};
});
But this doesn't work.... Nothing happens when i click on the remove button in the table..... But all this work fine when the button is out of the tag. Why is it so and how make it work?
I have created jsfiddle to illustrate my situation http://jsfiddle.net/alexrussinov/cs8RP/71/. What is strange that when I test this code on my local machine buttons in the table don't work but two separate under it, work fine. On jsfiddle, neither in the table nor below it, it doesn't work.
You can't have multiple elements on a page with the same ID, which is what you're doing with the button.
I think the simplest solution would be to stick the remove() call directly in the button tag with ng-click, passing in the line that you wish to remove. This of course assumes that your remove() method is part of $scope.
<button class="btn" ng-click="remove(line)"><i class="icon-remove"></i></button>

Resources