Angularjs | how in directive change classes for siblings of clicked element? - angularjs

I'm quite fresh with Angular, so sorry if my question looks trivial
I have few buttons, I create by ng-repeat. They have some bootstrap classes to make them nice, but if button is "active" gets additional class - active. The problem is only one button can be active, and I have 2 separate groups (containers with different class name) of buttons on page. They should work the same, but clicking on button on one groups should not affect the buttons in other group.
Could you, please help me write a directive, which will bind click event to button and remove 'active' class from all buttons in group and add it to clicked button only?
EDITED:
Here is a code in jsfiddle https://jsfiddle.net/996o7nk1/1/
HTML:
<!doctype html>
<html ng-app="confApp">
<head>
<title>Test</title>
<style>
.active {color: red;}
</style>
</head>
<body>
<div ng-controller="ProjectConfCtrl">
<div class="tans">
<button class="btn btn-default btn-cph" ng-repeat="tan in tans" ng-class="active: tan.active === true" data-tan-type-id="{{tan.id}}">{{tan.name}}</button>
</div>
<div class="langs">
<button class="btn btn-default btn-cph" data-ng-repeat="lang in langs">{{ lang.name }}</button>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="custom.js"></script>
</body>
</html>
JavaScript:
var app = angular.module("confApp", []);
app.controller('ProjectConfCtrl', [
'$scope', function($scope) {
$scope.tans = [
{
id: 1,
name: 'Auto',
active: true
}, {
id: 2,
name: 'Import',
active: false
}
];
$scope.langs = [
{
id: 1,
name: 'English',
active: true
}, {
id: 2,
name: 'Spanish',
active: false
}, {
id: 3,
name: 'German',
active: false
}
];
}
]);
For fiddle I just changed markup (removed html and head tag and added ng-app to div (it was in html)) but somehow in fiddle it throws an error.
I also added class, but it doesn't work also...

You can use ng-class together with a variable in the model, like in this fiddle:
https://jsfiddle.net/L5wm11s2/1/
Notice that I had to put the variables in the controller, not in the scope, and use the controller as to be able to access the variables from the view. That's because ng-repeat creates a child scope for each repeated element - a copy of the original scope, but separated from it. Because of that, if I did simply selectedTan = tan in ng-click, I'd end up setting the variable in the child scope. That means each button might consider itself selected, because it would have it's own selectedTan variable.

Related

Allow only one collapse div in ng-repeat

I have some data in the ng repeat, and inside that I have some data under each divs which is collapsed.
No when I click on the main div, I want only one div to collapse in at a time.
Eg: if I click abc, asdasd should be displayed.. Then if I click abc1, asdasd1 should be displayed but NOT asdasd
<script>
angular.module('myApp', [])
.controller("Ctrl_List", ["$scope", function(s) {
s.people = [
{name:"Sten", age:"49"}
,{name:"John", age:"39"}
,{name:"Hanne", age:"37"}
,{name:"Jens", age:"37"}
,{name:"Brian", age:"24"}
,{name:"Johnny", age:"24"}
,{name:"Peter", age:"49"}
]
s.obj = [
{
"name":'abc',
"text":'asdasd'
},
{
"name":'abc1',
"text":'asdasd1'
}
]
}])
html:
<body ng-app="myApp" ng-controller="Ctrl_List">
<div ng-repeat="ob in obj">
<button class="btn" data-toggle="collapse" href="#abc-{{ob.name}}"> {{ob.name}}</button>
<div id="abc-{{ob.name}}" class="collapse">{{ob.text}}</div>
</div>
</body>
data-parent is not working for me, or may be I am not using it properly.
Please Check the Fiddle here
Using a pure Angular approach rather than using JQuery for this.
Add a new property show to the each object and use ng-if to show/hide its corresponding text using a method in controller.
<div ng-repeat="ob in obj">
<button class="btn" ng-click=" showThis(ob)"> {{ob.name}}</button>
<div ng-if="ob.show">{{ob.text}}</div>
</div>
controller method
s.showThis = function(obj) {
//Hides all
angular.forEach(s.obj, function(ob) {
if(ob.name != obj.name) {
ob.show = false;
}
});
//Toggles current object show/hide
obj.show = !obj.show;
}
Working Fiddle

ng-show not hiding SPAN element in AngularJS 1.2

I am learning AngularJS from the book AngularJS up and running. This book uses AngularJS version 1.2. One of the sample codes in the books shows how ng-show directive works. The explanation of usage of ng-show directive in this example is given as follows:
ng-show inspect a variable and, depending on the truthiness of its
value, show or hide elements in the UI, respectively. In this case, we
say show the assignee span if note.assignee is true. AngularJS treats
true, nonempty strings, nonzero numbers, and nonnull JS objects as
truthy. So in this case, we get to see the assignee span if the note
has an assignee.
I am pasting the code below. (My query is given after the code.)
HTML:
<!-- File: chapter2/more-directives.html -->
<html ng-app="notesApp">
<head>
<title>Notes App</title>
<style>
.done {
background-color: green;
}
.pending {
background-color: red;
}
</style>
</head>
<body ng-controller="MainCtrl as ctrl">
<div ng-repeat="note in ctrl.notes"
ng-class="ctrl.getNoteClass(note.done)">
<span class="label"> {{note.label}}</span>
<span class="assignee"
ng-show="note.assignee"
ng-bind="note.assignee">
</span>
</div>
</body>
</html>
Script
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.19/angular.js">
</script>
<script type="text/javascript">
angular.module('notesApp', []).controller('MainCtrl', [
function() {
var self = this;
self.notes = [
{label: 'First Note', done: false, assignee: 'Shyam'},
{label: 'Second Note', done: false},
{label: 'Done Note', done: true},
{label: 'Last Note', done: false, assignee: 'Brad'}
];
self.getNoteClass = function(status) {
return {
done: status,
pending: !status
};
};
}]);
</script>
In the above code, an array is declared and there are two elements (first and and the last) which have some value in the 'assignee'. When I execute this code, I get to see all the elements of the array. The ng-show directive is not hiding the 'span' with a null value in the 'assignee'. Is the code wrong or my understanding of ng-show is wrong?
It should be like this
<div ng-show="note.assignee">
<span class="label"> {{note.label}}</span>
<span class="assignee" ng-bind="note.assignee"></span>
</div>

angular ui-bootstrap accordion how to achieve customized behavior: default all close, when clicking one accordion-group heading, open all others

I am working with ui-bootstrap accordion. I have an accordion inside an accordion.
under the 1st accordion 'Category', I have another accordion, with 3 accordion- group. They are closed by default. I would like this behavior: when you click an accordion group title (say 'fruit'), if all the accordion groups are closed, then it will open them all... but if any groups is open( say if any of the 'fruit', 'meat', and vegetable' is open when user is clicking), then when you click, it will toggle the clicked accordion-group. you can check out the plunker here:
http://plnkr.co/edit/UcETfOVh8RwMX40mOCVv?p=preview
my html and angular code is follow:
<!DOCTYPE html>
<html ng-app="plunker">
<body ng-controller="AccordionDemoCtrl">
<accordion >
<accordion-group>
<accordion-heading> Category</accordion-heading>
<accordion ng-repeat="(category, items) in categories" close-others="oneAtATime" is-open="true">
<accordion-group >
<accordion-heading ><div>{{category}}</div></accordion-heading>
<div ng-repeat="item in items.data">{{item}}</div>
</accordion-group>
</accordion>
</accordion-group>
</accordion>
</body>
</html>
angular.module('plunker', ['ui.bootstrap']);
function AccordionDemoCtrl($scope) {
$scope.categories = {
fruit: {
data: {
apple: 3,
orange: 5,
lemon: 6
},
toggled: false
},
vegetable: {
data: {
lettuce: 1,
broccoli: 5,
spinach: 4
},
toggled: false
},
meat: {
data: {
chicken: 3,
beef: 6,
lamb: 8
},
toggled: false
}
};
}
How can I achieve such behavior? I have work progress in another plunker:
http://plnkr.co/edit/9eTfpn81g57Dk4FtVSlA?p=preview
I've explored with 'is-open' attribute in accordion, I've tried to refered to the $parent, not producing the behavior i want.
As mentioned by bobleujr is-open should work. But the code is a bit tricky and I'm not sure if there is an easier solution.
The problem is that the ng-click where I'm adding my 'toggleAll' check is called after toggeling the clicked accordion.
So I have to track the previous accordion state to be sure that we have to toggle all accordions. I'm doing this by looping over all accodions states and count if it is open.
Then I can check later if previousCount == 0 and currentCount==1 we need to open all.
I don't like doing two for loops for the behaviour but I think that's the only way to do it.
If it wouldn't toggle the state with ng-click it would be easier to do. But I don't know how. I've tried to disable the is-open toggle of the accordion directive but that wasn't working.
Decorating of the accordion directive could help then you could add the check before toggeling. But that's probably not that easy.
Please have a look at the demo below or here in the updated plunkr.
// Code goes here
angular.module('plunker', ['ui.bootstrap'])
.controller('AccordionDemoCtrl', AccordionDemoCtrl);
function AccordionDemoCtrl($scope) {
var prevOpenCount = 0;
$scope.toggle = function(index) {
var openCount = 0,
openAll = false;
// check how many accordions are open
for( var i=0; i < $scope.openState.length; i++ ){
if ($scope.openState[i] === true) {
openCount++;
}
}
if ( openCount === 1 && prevOpenCount === 0) { // open all
for( var i=0; i < $scope.openState.length; i++ ){
$scope.openState[i] = true;
}
}
//console.log(index, $scope.openState, openCount, prevOpenCount);
prevOpenCount = openCount;
};
$scope.categories = {
fruit: {
data: {
apple: 3,
orange: 5,
lemon: 6
},
toggled: false
},
vegetable: {
data: {
lettuce: 1,
broccoli: 5,
spinach: 4
},
toggled: false
},
meat: {
data: {
chicken: 3,
beef: 6,
lamb: 8
},
toggled: false
}
};
$scope.openState = new Array(Object.keys($scope.categories).length);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular.js"></script>
<!--<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.9.0.js"></script>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular-animate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.3/ui-bootstrap-tpls.js"></script>
<!--<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
--><script src="script.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<div ng-app="plunker" ng-controller="AccordionDemoCtrl">
<accordion>
<accordion-group>
<accordion-heading> Category</accordion-heading>
<accordion ng-repeat="(category, items) in categories" close-others="oneAtATime">
<accordion-group ng-click="toggle($index, $event)" is-open="openState[$index]">
<accordion-heading ><div><!--{{openState}}-->{{category}}</div></accordion-heading>
<div ng-repeat="item in items.data">{{item}}</div>
</accordion-group>
</accordion>
</accordion-group>
</accordion>
</div>
WABBIT0111
you may want to use the attribute is-open=ctrlVar.
This way you will be able to set it with a ng-click=action to change ctrlVar state and then toggle them back to original position.

One time binding: update model and re-render view

I was wondering if possible, using angular one time binding, to completely re-render the view/template after a model update, also by recompiling the template.
For instance, on a button press, maybe in the way react works: I update the model and explicitly force to update the view.
Basically here is what I am trying to achieve:
// controller
angular.module('app', []).controller('AppCtrl', function($scope) {
$scope.items = [
{id: 1},
{id: 2},
{id: 3}
];
$scope.addAndRefresh = function() {
$scope.items.push({id: 4});
// manually call render logic here???
};
});
<!-- HTML template -->
<div ng-repeat="item in ::items">
{{item.id}}
</div>
<button ng-click="addAndRefresh()">Add</button>
By clicking on the "Add" button I would like to refresh the view to see the newly added item.
I was trying to figure out some way to do this elegantly as well. I wish there was something built into the framework to refresh one-time bindings. All I came up with is using ngIf to remove the element I wanted to refresh and the add it back.
Here's a demo. Click the Add Item button, you'll see that the list does not refresh due to the one-time binding on the repeat. Check the refresh values and click again, and the items will be updated:
var app = angular.module('demo', []);
app.controller('RefreshCtrl', function($scope, $timeout) {
var counter = 4;
$scope.visible = true;
$scope.items = ['Item1', 'Item2', 'Item3'];
$scope.addItem = function() {
if ($scope.refresh) {
$scope.visible = false;
}
$scope.items.push('Item' + counter);
counter++;
$timeout(function() {
$scope.visible = true;
});
};
});
<script src="https://code.angularjs.org/1.3.17/angular.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<div ng-app="demo" ng-controller="RefreshCtrl" class="container">
<button class="btn btn-default" ng-click="addItem()">Add Item</button>
<input type="checkbox" ng-model="refresh" />Refresh Values
<div ng-if="visible">
<h3 ng-repeat="item in ::items">{{item}}</h3>
</div>
<p>Items Array: {{items}}</p>
</div>
Depending on what you are after, I would recommend one of two solutions:
Get angular-bind-notifier.
Does not recompile your template, only refreshes the bound values.
Get kcd-recompile.
Recompiles the template along with the bound values.
I'm the author of the former, and the big difference between it and other solutions is the choice of hooking into the $parse service.
As such, you can use the introduced {{:refreshkey:expression}}/:refreshkey:expression syntax in most (if not all) areas of Angular where an expression is accepted.
In your case, the implementation could look something like this:
js
angular.module('app', []).controller('AppCtrl', function($scope) {
$scope.items = [
{id: 1},
{id: 2},
{id: 3}
];
$scope.addAndRefresh = function() {
$scope.items.push({id: 4});
/**
* '$$rebind' is the internal namespace used by angular-bind-notifier.
* 'refresh' is the refresh key used in your view.
*/
$scope.$broadcast('$$rebind:refresh');
};
});
markup
<!-- HTML template -->
<div ng-repeat="item in :refresh:items">
{{::item.id}}
</div>
<button ng-click="addAndRefresh()">Add</button>
Or, if you wanted something semi-dynamic
js
angular.module('app', []).controller('AppCtrl', function($scope) {
$scope.items = [
{id: 1},
{id: 2},
{id: 3}
];
$scope.add = function() {
$scope.items.push({id: 4});
};
});
markup
<!-- HTML template -->
<div bind-notifier="{ refresh: items.length }">
<div ng-repeat="item in :refresh:items">
{{::item.id}}
</div>
</div>
<button ng-click="add()">Add</button>
Check out the README and this jsBin for some usage examples.

AngularJS Accordion Expand All Collapse All

I am trying to get the accordions to toggle correctly through the directive ng-click. If I have Item one open how do I get it to expand all the accordions? Item two and Item three will continue to expand and collapse but Item one stays stagnant.
Plunker
alternately you can adjust your buttons so that they just loop through the children.
html:
<div ng-controller="AccordionDemo">
<div >
<div class="stuff_in_the_middle" >
<div ng-repeat="m in results" ng-click="m.open = !m.open" style="margin-bottom:20px">
<div heading="{{m.label}}" is-open="m.open" style="background-color:#d2d2d2; cursor:pointer" >
{{m.label}}
</div>
<div ng-show="m.open" style="padding:10px">
contents
</div>
</div>
<span class="btn btn-default" ng-click="toggle(false)">Collapse All</span>
<span class="btn btn-default" ng-click="toggle(true)">Expand All</span>
</div>
<hr />
</div>
</div>
JS:
var module = angular.module('plunker', []);
module.controller('AccordionDemo', ['$scope',
function ($scope) {
$scope.results = [
{label: 'Item 1', open: false},
{label: 'Item 2', open: false},
{label: 'Item 3', open: false}
];
$scope.toggle = function(state) {
$scope.results.forEach(function(e) {
e.open = state;
});
}
}
]);
see it working here: http://plnkr.co/edit/T6iv7mSoat9SQBwSIFJP
You have a problem with scopes. Simple rule is to never set variable value from ng-click or similar directives if you gonna use this variable outside - in parent.
It is caused by ng-repeat, which creates scope and if you will try to define new variable within it(you are doing it, because you have used name plunker, instead of opened), it will be defined only to current item in repeat.
You can use setter function to set it to right scope. So, here we go: http://plnkr.co/edit/h3MtKywiOaIQhpnAzWLT?p=preview

Resources