AngularJS give active class to button in button group - angularjs

I have a button group with some buttons, two created with html and others are created with a ng-repeat. I want that on click the button have an active class so I can custom it to show it's activated.
So here is what I do :
<div class="btn-group" role="group" aria-label="Basic example"
ng-init="selectedTab = 'raw'">
<button class="btn"
ng-click="selectView('raw'); selectedTab = 'raw'; console.log(selectedTab);"
ng-class="{'active':selectedTab === 'raw'}">Raw data
</button>
<button class="btn"
ng-click="selectView('summary'); selectedTab = 'summary'; console.log(selectedTab);"
ng-class="{'active':selectedTab === 'summary'}">Summary
</button>
<button class="btn" ng-repeat="(key, value) in views"
ng-click="selectView(key); selectedTab = key; console.log(selectedTab);"
ng-class="{'active':selectedTab === key}">
{{ key }}
</button>
</div>
My problem is that for the two first one all works fine, when I click on the first button the class active is added, and when I click on the second the class is removed from the first one and added to the second one.
The problem is about the buttons generated by the ng-repeat, when I click on them it's add the active class to the button but when I click on another button it's not removing the class, so they can all have the activate class.
What am I doing wrong ?

Remember that ng-repeat creates its own local scope so any variables you reference there that are not defined on the parent scope will be created locally. Although you can put multiple commands in ng-click it is discouraged - it also seems that doing so will not cause a digest cycle for those items inside the ng-repeat.
You can resolve all of this by making selectedTab a property on your controller and having the selectView method set the value of selectedTab. Here's a quick example:
angular.module('app', [])
.controller('ctrl', ($scope) => {
$scope.selectedTab = 'raw';
$scope.views = {
Detail1: 'details',
Detail2: 'details',
Detail3: 'details'
};
$scope.selectView = function(view) {
$scope.selectedTab = view;
}
});
.active {
color: red !important;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div class="btn-group"
role="group"
aria-label="Basic example">
<button class="btn"
ng-click="selectView('raw')"
ng-class="{'active':selectedTab === 'raw'}">Raw data</button>
<button class="btn"
ng-click="selectView('summary')"
ng-class="{'active':selectedTab === 'summary'}">Summary</button>
<button class="btn"
ng-repeat="(key, value) in views"
ng-click="selectView(key)"
ng-class="{'active':selectedTab === key}">
{{ key }}
</button>
</div>
</div>

New AngularJS developers often do not realize that ng-repeat, ng-switch, ng-view, ng-include and ng-if all create new child scopes, so the problem often shows up when these directives are involved.
The assignment selectedTab = key is being done on the child scope created by the ng-repeat directive. The solution is to do the assignment to a property of an object:
<div class="btn-group" role="group" aria-label="Basic example"
ng-init="selected = {tab:'raw'}">
<button class="btn"
ng-click="selectView('raw'); selected.tab = 'raw';"
ng-class="{'active':selected.tab === 'raw'}">Raw data
</button>
<button class="btn"
ng-click="selectView('summary'); selected.tab = 'summary';"
ng-class="{'active':selected.tab === 'summary'}">Summary
</button>
<button class="btn" ng-repeat="(key, value) in views"
ng-click="selectView(key); selected.tab = key;"
ng-class="{'active':selected.tab === key}">
{{ key }}
</button>
</div>
For more information, see What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

Related

AngularJS- How to handle each button created by ng-repeat

I am new to AngularJS.
I have created <li> to which I used ng-repeat.
<li> contains images and buttons like like, comment and share which is inside <li> and created by ng-repeat.
I have made function which will replace empty like button to filled like button (By changing background image of button).
But problem is this trigger applies to only first like button and other buttons does not change.
How can I fix this?
Code:
HTML:
<html>
<body>
<ul>
<li ng-repeat="media in images"><div class="imgsub">
<label class="usrlabel">Username</label>
<div class="imagedb">
<input type="hidden" value="{{media.id}}">
<img ng-src="{{ media.imgurl }}" alt="Your photos"/>
</div>
<!-- <br><hr width="50%"> -->
<div class="desc">
<p>{{media.alt}}</p>
<input type="button" class="likebutton" id="likeb" ng-click="like(media.id)" ng-dblclick="dislike(media .id)"/>
<input type="button" class="commentbutton"/>
<input type="button" class="sharebutton"/>
</div>
</div> <br>
</li><br><br><br>
</ul>
</body>
</html>
JS:
$scope.like = function(imgid)
{
document.
getElementById("likeb").
style.backgroundImage = "url(src/assets/like-filled.png)";
alert(imgid);
}
$scope.dislike = function(imgid)
{
document.
getElementById("likeb").
style.backgroundImage = "url(src/assets/like-empty.png)";
}
Thanks for help & suggestions :)
The id for each button should be unique but in your case, it's the same for all buttons ('likeb').
You can set the value of the attribute 'id' for each button dynamically by using '$index' and passing '$index' to the functions as follows:
<input type="button" class="likebutton" id="{{$index}}" ng-click="like($index)" ng-dblclick="dislike($index)"/>
Then in your controller, you can use the functions with the passed value.
For example,
$scope.like = function(index)
{
document.
getElementById(index).
style.backgroundImage = "url(src/assets/like-filled.png)";
}
Another good alternative in your case would be to use the directive ngClass.
use 2 css class for styling liked and disliked state, and then put the class conditionally with ng-class instead of DOM handling. and if you really want to perform a DOM operation (I will not recommend) then you can pass $event and style $event.currentTarget in order to perform some operation on that DOM object.

Angular toggle class on a different template

In my header.html page I have this button:
<button class="tabs-menu-btn" ng-click="toggle = !toggle" ng-class="{'open' : toggle}">
<div></div>
</button>
And on wrapper.html I have this div:
<div class="tabs-toolbar" ng-class="{'open' : toggle}">
<ul class="tabs">
<li class="tab">
<a ng-click="toggle = !toggle" ui-sref-active="selected" ui-sref="flows"><span>Flows</span></a>
</li>
</ul>
</div>
So when the button is clicked I want to toggle the 'open' class on the button and div - but currently it only works on the button, if i move the 'tabs-toolbar' onto the same html page it works. Does anyone know what I am doing wrong please?
header and wrapper are part of two different scope. So when you click toggle its is changing only the $scope.header.toggle = false
sample Example:
$scope.header.toggle = true
$scope.wrapper.toggle = false // its not getting reflected because, this scope will not get effected with the action
Solution
make both variable as part of same controller or trigger a function which changes both scope

How to bind a part of a variable already binded

I have a loop ng-repeat that displays sevral icons.
<div class="box">
<div class="box-body">
<div class="row" >
<div class="col-sm-6" style="margin-bottom: 5px;" ng-repeat="record in newlayout.display" align="center">
<a class="btn btn-app" ng-href="#newlayout/{{newlayout.url}}{{newlayout.itemValue}}" >
<span class="badge bg-yellow" style="font-size:22px;">{{record.numberOfSamples}}</span>
<i class="fa fa-{{newlayout.labStyle}}"></i> {{record.lab}}
</a>
</div>
</div>
</div>
</div>
My issue is that the second part of the binded variable itemValue should be dynamic
In my Js, I have this
newLayout.url = 'sublabs/?labName=';
newLayout.itemValue = 'record.lab';
The URL is dynamic.
When I click on the first displayed Icon, the url should look like this :
But it didn't work as I had a compilation error..
Does someone have an idea how to fix this:
http://localhost:8181/#/newlayout/sublabs?labName=PIA/C1 - Shiftlabo
Where the record value "PIA/C1 - Shiftlabo" change.
So basically here if I change
<a class="btn btn-app" ng-href="#newlayout/{{newlayout.url}}{{newlayout.itemValue}}" >
{{newlayout.itemValue}} by {{record.lab}} it would work..but the {{record.**lab**}} should be dynamic as it will have another value when I click on the icon. It will change to {{record.subLab}}
Thanks
Use property acccessor bracket notation inside the binding:
<div>{{record[labOrSublab]}}</div>
JS
var isSublab = false;
$scope.labOrSublab = "lab";
$scope.clickHandler = function() {
isSublab = !isSublab;
$scope.labOrSublab = isSublab ? 'subLab' : 'lab';
};

angular ng-focus / focus input element

I have three input elements styled as buttons. On ng-keyup I invoke a function in my controller.
Now the problem is I have to manually click on one of them to get a focus, only then keyUp works.
I tried ng-focus='focus' and then setting $scope.focus=true in controller, it didn't work.
<div class="row startButtonRow">
<div class="col col-50" align="center">
<button class="button button-dark startButton" ng-click="start()" ng-disabled="disableStart">Start</button>
</div>
<div class="col" align="center">
<input ng-focus="focus" type="button" class="button button-dark startButton" ng-disabled="disableStop" ng-keyup="stopTimer($event)">
</div>
<div class="col" align="center">
<input type="button" class="button button-dark startButton" ng-disabled="disableStop" ng-keyup="stopTimer($event)">
</div>
<div class="col" align="center">
<input type="button" class="button button-dark startButton" ng-disabled="disableStop" ng-keyup="stopTimer($event)">
</div>
</div>
Once I click Start button the symbols start appearing on three columns and I am supposed to press corresponding button (here input element with key-up) as soon a specific symbol appear. I don't want to click on the element first and then being able to use keyboard keys.
ng-keyup does not have to go on one of your input elements, you could move it to your body element, or whatever your ng-app element is, and it will still catch keypresses.
On a different note, I believe you are misunderstanding what ng-focus does. It evaluates the given expression when the input is focused, it is not telling your browser to focus that element. You will want to build a custom directive to focus your inputs, see How to set focus on input field?
You could add something like this to your controller:
var handler = function(e){
if (e.keyCode === 37) {
// left arrow
} else if (e.keyCode === 38) {
// up arrow
} else if (e.keyCode === 39) {
// right arrow
}
};
var $doc = angular.element(document);
$doc.on('keyup', handler);
$scope.$on('$destroy',function(){
$doc.off('keyup', handler);
})

Animate rows through a directive inside a ng-repeat

I'm building this application in Angular where a div table is formed by using ng-repeat through the following html:
HTML
<div ng-repeat="(key, value) in data.data.ipv4">
<div class="cellbody">{{value.descr}}</div>
<div class="cellbody">{{value.protocol}}</div>
<div class="cellbody">{{value.internip}}</div>
<div class="cellbody">{{value.internrange}}</div>
<div class="cellbody">{{value.externrange}}</div>
<div class="deletecell">
<span class="toggledelete" ng-click="deleteport($event, key, 4)">
<i class="icon-minus negativehover"></i>
</span>
<span class="toggledelete" style="display:none">
<span>PORT DELETED</span>
<span class="deletedportundo" ng-click="restoreport($event, $index, 4)">
UNDO
</span>
</span>
</div>
</div>
The last div of each row, has a visible clickable button which sends a delete order to the server via the deleteport() function, and then, if everything goes all right, starts an animation where the whole cell is hidden and the previously hidden span with class 'deletedportundo' shows up.
Anyway the thing is my controller looks like this:
Angular Javascript Controller
$scope.deleteport = function(e,f) {
postData.index = f;
$http.post('serverside/router.php', postData)
.success(function(data, status, headers, config){
if (data.status == 'ok') {
var elem = angular.element(e.target);
$(elem).parent().parent().parent().children('.cellbody').hide('fast');
$(elem).parent().parent().children('.toggledelete').toggle();
$(elem).parent().parent().parent().children('.deletecell').animate({
width: "100%"
}, 300 );
$(elem).parent().parent().parent().children('.deletecell').addClass('macdeleted');
}
});
}
Which visually works as expected, except that I am aware that I should not be manipulating the DOM in the controller; I have been unsuccessfully trying to integrate this into a directive, but because every row is independent of the others I have not been able to achieve the desired effect.
Daniel,
There is nothing wrong with manipulating DOM in the controller. However, I can suggest an easier way to do it than to navigate with those nasty parent().parent().parent().parent().parent() ... calls :) .
Just add an ng-show to the toggledelete div, than just do $scope.portDeleted = false in your controller. This also works for the .cellbody tags.
As for the .deletecell class you can use ng-class, and just do $scope.deletecell = some_value.
<div ng-class="{deletecell:deletecell}">
<span class="toggledelete" ng-click="deleteport($event, key, 4)">
<i class="icon-minus negativehover"></i>
</span>
<span class="toggledelete" style="display:none">
<span>PORT DELETED</span>
<span class="deletedportundo" ng-click="restoreport($event, $index, 4)">
UNDO
</span>
</span>
</div>
For animate, you can use ngAnimate (http://www.nganimate.org/)

Resources