Active button states in Angular + Bootstrap - javascript

Seems like a simple problem but I'm actually having trouble with it.
Plunk here
Basically I have a ng-repeat of buttons and then a block of text after that clicking the button will show. However, when I click a button I want to hide the blocks of text from all the other buttons and remove the active states from the other buttons. Basically only 1 button block of text should be shown at a time.
Seems easy, but the way ng-hide handles scope (scope: true) means I can't really look into the other scopes and turn each of them off. The other thing is that I don't want to alter the actual array from ng-repeat if at all possible. This is data from an API that I have to send back and I'm attempting to not alter the actual data structure if I can.
<div class="row" ng-repeat="button in buttons">
<div class="col-sm-2">
<button ng-click="showInfo = !showInfo" class="btn btn-primary">{{button.name}}</button>
</div>
<div ng-show="showInfo" class="col-sm-3">
<div class="alert alert-success">{{button.extraInfo}}</div>
</div>
</div>
And JS
app.controller('MainCtrl', function($scope) {
$scope.buttons = [
{ name: 'One', extraInfo: 'Extra info for button 1' },
{ name: 'Two', extraInfo: 'Extra info for button 2' },
{ name: 'Three', extraInfo: 'Extra info for button 3' }
];
});

I suggest to create new array which has the same length as buttons array, and this array will hold boolean values to indicate where the item active or not.
I didn't log in to plunk so here the modified version of yours.
index.html
<body ng-controller="MainCtrl as vm">
<div class="row" ng-repeat="button in buttons track by $index">
<div class="col-sm-2">
<button ng-click="vm.setActive($index)" ng-class="vm.isActive[$index] ? 'btn btn-primary' : 'btn'">{{button.name}}</button>
</div>
<div ng-show="vm.isActive[$index]" class="col-sm-3">
<div class="alert alert-success">{{button.extraInfo}}</div>
</div>
</div>
</body>
app.js
app.controller('MainCtrl', function($scope) {
$scope.buttons = [
{ name: 'One', extraInfo: 'Extra info for button 1' },
{ name: 'Two', extraInfo: 'Extra info for button 2' },
{ name: 'Three', extraInfo: 'Extra info for button 3' }
];
var vm = this;
vm.isActive =[];
for(var i=0, len=$scope.buttons.length; i < len; i++){
vm.isActive[i] = false;
}
vm.setActive = function(ind) {
for(var i=0, len=vm.isActive.length; i < len; i++){
vm.isActive[i] = i===ind;
}
}
});

If you don't want to change the actual array, then maintain another object or array which will hold the key to each button's show/hide state.
$scope.showInfo = {};
$scope.buttons = [
{ name: 'One', extraInfo: 'Extra info for button 1' },
{ name: 'Two', extraInfo: 'Extra info for button 2' },
{ name: 'Three', extraInfo: 'Extra info for button 3' }
];
$scope.changeShowInfo = function(index) {
for(var prop in $scope.showInfo) {
$scope.showInfo[prop] = false;
}
$scope.showInfo[index] = true;
};
Solved Plunker

You want 1 button active each time, so you better use radio buttons with a currentItem kept in the scope by using ng-bind.
HTML:
<body ng-controller="MainCtrl">
<div name="myForm">
<div ng-repeat="button in buttons">
<label>
<input type="radio" ng-model="$parent.selectedItem" ng-value="button"> {{button.name}}
</label>
</div>
</div>
<div class="alert alert-success">Extra info: {{selectedItem.extraInfo}}</div>
</body>
Didn't need to change your JS.
See Plunker here

Related

Angular: Showing only checked items in a checkbox list

Is it possible to show only a list of checked items in a checkbox list?
What I want to do is select a few items on a checked list and when I press "Show only checked items", I want to toggle between showing only the checked items in the checkbox list and showing the entire list with the checked items.
I searched angular's site but wasn't able to find a solution to it.
Fiddle: http://jsfiddle.net/fjoLy5sq/422/
<div ng-controller="DemoCtrl">
<label ng-repeat="role in roles">
<input type="checkbox" checklist-model="user.roles" checklist-value="role.id"> {{role.text}}
</label>
<br>
<button ng-click="checkAll()">check all</button>
<button ng-click="uncheckAll()">uncheck all</button>
<button ng-click="checkFirst()">check first</button>
<button ng-click="checkFirst()">Show only Checked</button>
<br><br>
user.roles {{ user.roles | json }}
</div>
Javascript:
angular.module("DemoApp", ["checklist-model"])
.controller('DemoCtrl', function($scope) {
$scope.roles = [
{id: 1, text: 'guest'},
{id: 2, text: 'user'},
{id: 3, text: 'customer'},
{id: 4, text: 'admin'}
];
$scope.user = {
roles: [2, 4]
};
$scope.checkAll = function() {
$scope.user.roles = $scope.roles.map(function(item) { return item.id; });
};
$scope.uncheckAll = function() {
$scope.user.roles = [];
};
$scope.checkFirst = function() {
$scope.user.roles.splice(0, $scope.user.roles.length);
$scope.user.roles.push(1);
};
});
Add a new variable in controller:
$scope.showAll = true;
In a view inverse the value of showAll when Show only Checked button is clicked:
<button ng-click="showAll = !showAll">Show only Checked</button>
To show only checked items, use Array.includes method, and check that current role is in user.roles:
<label ng-repeat="role in roles" ng-if="user.roles.includes(role.id)">
<input type="checkbox" checklist-model="user.roles" checklist-value="role.id"> {{role.text}}
</label>
Working demo

Handling dropdown in angular js

I have a dropdown with some values :
Apple
Mango
Orange
Grapes
HTML :
<div class="form-group">
<label class="control-label col-sm-20" for="groupz">Role*</label>
<select class="form-control" ng-model="model.selectedRole" name="role" ng-change="GetRole(model.selectedRole)" >
<option value class selected>Select Roles</option>
<option ng-repeat="item in model.roles track by $index" value="{{item}}">{{item}}</option>
</select>
</div>
I want my $scope.selectedRole to be by default as Apple. But later when the user needs to change the value, they can change it from apple to orange or any other fruit name. I have separate service request to fetch the fruits from backend and I write my code in controller as follows.
$scope.GetRole = function() {
$scope.selectedrole = [];
if ($scope.model.selectedRole != null) {
for (var i = 0; i < 1; i++) {
$scope.selectedrole.push($scope.model.selectedRole);
}
}
}
I hope this helps you
JsFiddle
In js
angular.module('ExampleApp', [])
.controller('ExampleController', function($scope) {
$scope.selectedrole = ['Apple', 'Mango', 'Orange', 'Grapes'];
$scope.selectRole= $scope.selectedrole[0];
});
In HTML
<div ng-app="ExampleApp">
<div ng-controller="ExampleController">
<select ng-model="selectRole" ng-options="role for role in selectedrole">
</select>
</div>
just try : HTML
<select class="form-control select" name="role" id="role" data-ng-model="ctrl.model.selectedRole" data-ng-options="option.name for option in ctrl.model.roles track by option.id"></select>
in your contoller
$scope.model = {
roles: [{
id: '1',
name: 'Apple'
}, {
id: '2',
name: 'Orange'
}, {
id: '3',
name: 'Mango'
}],
selectedRole: {
id: '1',
name: 'Apple'
} //This sets the default value of the select in the ui
};
Then assign the first array element to selectedrole containing the array of values(Apple Mango Orange Grapes).
If you want the default to be apple and the array is ordered:
array = [Apple, Mango, Orange, Grapes]
Your model needs to be set to selectedRole:
data-ng-model="selectedRole"
In your controller, set:
selectedRole = array[0]
angular will take care of the rest of the data manipulation.
I hope this helps. Please provide more information for a clearer answer
Thanks
Handling a select element i.e. a drop down list in AngularJS is pretty simple.
Things you need to know is that you bind it to an array or a collection to generate the set of option tags using the ng-options or the ng-repeat directives which is bound to the data source on your $scope and you have a selected option which you need to retrieve as it is the one the user selects, it can be done using the ng-model directive.
If you want to set the selected option on the page load event, then you have to set the appropriate object or value (here it is the fruit id) which you are retrieving from data binding using the as clause in the ng-options directive as shown in the below embedded code snippet
ng-options="fruit.id as fruit.name for fruit in ctrl.fruits"
or set it to the value of the value attribute when using the ng-repeat directive on the option tag i.e. set data.model to the appropriate option.value
<select size="6" name="ngvalueselect" ng-model="data.model" multiple>
<option ng-repeat="option in data.availableOptions" ng-value="option.value">{{option.name}}</option>
</select>
angular
.module('fruits', [])
.controller('FruitController', FruitController);
function FruitController() {
var vm = this;
var fruitInfo = getFruits();
vm.fruits = fruitInfo.fruits;
vm.selectedFruitId = fruitInfo.selectedFruitId;
vm.onFruitChanged = onFruitChanged;
function getFruits() {
// get fruits and selectedFruitId from api here
var fruits = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Mango' },
{ id: 3, name: 'Banana' },
{ id: 4, name: 'Orange' }
];
var fruitInfo = {
fruits: fruits,
selectedFruitId: 1
};
return fruitInfo;
}
function onFruitChanged(fruitId) {
// do something when fruit changes
console.log(fruitId);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="fruits">
<div ng-controller="FruitController as ctrl">
<select ng-options="fruit.id as fruit.name for fruit in ctrl.fruits"
ng-model="ctrl.selectedFruitId"
ng-change="ctrl.onFruitChanged(ctrl.selectedFruitId)">
<option value="">Select Fruit</option>
</select>
</div>
</div>
Check the Example section here for more information.

Angular: How to use ng-options to make four separate selections?

JS:
angular
.module('app', [])
function MainCtrl() {
var ctrl = this;
ctrl.selectionList = [
{ id: 1, name: 'apple'},
{ id: 2, name: 'banana'},
{ id: 3, name: 'grapes'},
{ id: 4, name: 'carrot'}
];
ctrl.selectedThing = ctrl.selectionList[0].name;
}
angular
.module('app', [])
.controller('MainCtrl', MainCtrl);
HTML:
<div class="row">
<div class="col-sm-3 col-xs-12 unit">
<select
ng-model="ctrl.selectedThing"
ng-options="selections.name as selections.name for selections in ctrl.selectionList">
</select>
</div>
</div><!--end of first row-->
So this code creates four different selections.
The problem is that when I choose an option, let's say for example "apples" on one selection, all the other selections become apples too. Is there any way to solve this with ng-options or should I just write the select in HTML?
You definitely want to use ng-options, as that isn't the issue here. The problem you are seeing is most likely because the ng-model on all of your select elements is the same ctrl variable. So when you update one of them, it changes a single variable that is bound to all four dropdowns. You either need to setup up an array for your selected items, or four different instances of a selected variable.
ctrl.selectedThings = [ctrl.selectedList[0].name, '', '', ''];
Then in your view you can do this...
<select
ng-model="ctrl.selectedThings[rowIndex]"
ng-options="selections.name as selections.name for selections in ctrl.selectionList">
</select>
Not the most robust solution if you are going past 4 items, but you should be able to adapt it to be dynamic.
Your code is working fine, can you check and confirm?!
(function ()
{
var app = angular.module("app", []);
function HomeController()
{
var vm = this;
vm.selectionList = [
{ id: 1, name: 'apple'},
{ id: 2, name: 'banana'},
{ id: 3, name: 'grapes'},
{ id: 4, name: 'carrot'}
];
}
app.controller("HomeController", [HomeController]);
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE html>
<html lang="en" ng-app="app">
<head>
<meta charset="UTF-8">
<title>Angular JS App</title>
</head>
<body>
<div class="container" ng-controller="HomeController as homeCtrl">
<div class="row">
<div class="col-sm-3 col-xs-12 unit">
<select
ng-model="homeCtrl.selectedThing"
ng-options="selections.name as selections.name for selections in homeCtrl.selectionList">
</select>
<pre>{{homeCtrl.selectedThing}}</pre>
</div>
</div><!--end of first row-->
</div>
</body>
</html>
If you have ng-model="ctrl.selectedThing" for all of your <select> tags, they will all change to the same selection because they're using the same scope property. Think of it like having 4 variables referencing the same data: if you change one, access any of the variables will retrieve the same result.
You need to bind all of your selects to a different property on scope, so ctrl.selectedThing1,2,...n. That's not very scalable, but that would fix your problem.

Selected ng-options item doesn't stick to 'combo box'

I'm new to AngularJS and am struggling with something that should be easy.
I have this ugly looking page:
Users add new beers on this screen and a beer may have more than one style - so the 'Add' button creates a new 'Style' combo box and the 'Remove' on the left side, well, removes it.
Problem is when the user selects one style from the list it doesn't appear as selected in the combo box - ie, the style field will be empty after the dropdown list has been collapsed/closed again.
So here is my code:
Beer View
<div class="form-group" ng-repeat="style in beer.styles">
<label for="style" class="col-sm-1 control-label" ng-if="$index == 0" >Style</label>
<div class="col-sm-1" ng-if="$index > 0">
<button type="button" class="btn btn-danger" ng-click="removeStyle($index)">Remove</button>
</div>
<div class="col-sm-10">
<select class="form-control"
ng-controller="styleCtrl"
ng-model="selectedStyleId"
ng-options="style.id as style.name for style in styles"
ng-change="updateStyle($index, selectedStyleId)">
</select>
</div>
<div class="col-sm-1">
<button type="button" class="btn btn-primary" ng-click="addNewStyle()">Add</button>
</div>
</div>
Style Controller (Dummy Impl)
.controller('styleCtrl', ["$scope", "$location", function ($scope, $location) {
$scope.styles = [
{ 'id': '1',
'name': 'Lager' },
{ 'id': '2',
'name': 'American Blonde Ale' },
{ 'id': '3',
'name': 'American Stout' },
{ 'id': '4',
'name': 'Cream Ale' },
{ 'id': '5',
'name': 'Bock' },
{ 'id': '6',
'name': 'German Pilsener' }
];
}]);
Beer Controller
.controller('beerCtrl', ["$scope", "$location", function ($scope, $location) {
$scope.beer = {
'styles': [
{ 'id' : '-1' }
]
}
$scope.addNewStyle = function() {
$scope.beer.styles.push({ 'id': '-1' });
}
$scope.removeStyle = function(index) {
$scope.beer.styles.splice(index, 1);
}
$scope.updateStyle = function(index, styleId) {
$scope.beer.styles[index] = { 'id': styleId };
}
The styleId is correctly set in the array, the only problem really is in the screen.
Have you spotted anything wrong in my code?
P.S.: I suspect the fact I have two controllers - the BeerController and a 'nested' StyleController in the same view may be related to the root cause.
As I mentioned I'm brand new to AngularJS so my mistake was rather simple. Instead of using on-change, I've binded the selected value to the backing object directly:
<select class="form-control"
ng-controller="styleCtrl"
ng-model="beer.styles[$index].id"
ng-options="style.id as style.name for style in styles">
That got the job done. Silly oversight.

Angular - can I use a single function on multiple objects within a nested ng-repeat that triggers the ng-show of just that object?

SUMMARYI have a list of brands and a list of products. I am using an ng-repeat to show the list of brands, and an ng-repeat with a filter to show the list of products within their respective brands. I want each brand and each product to have a button that shows more about that brand/product. All of these buttons should use the same function on the controller.
PROBLEMThe button that shows more about the brand also shows more about each of that brand's products, UNLESS (this is the weird part to me) I click the button of a product within that brand first, in which case it will work correctly.
CODEPlease see the Plunker here, and note that when you click on 'show type' on a brand, it also shows all the types of the products within that brand: http://plnkr.co/edit/gFnq3O3f0YYmBAB6dcwe?p=preview
HTML
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="MyController as vm">
<div ng-repeat="brand in brands">
<h1>
{{brand.name}}
</h1>
<button ng-click="showType(brand)">
Show Brand Type
</button>
<div ng-show="show">
{{brand.type}}
</div>
<div ng-repeat="product in products
| filter:filterProducts(brand.name)">
<h2>
{{product.name}}
</h2>
<button ng-click="showType(product)">
Show Product Type
</button>
<div ng-show="show">
{{product.type}}
</div>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<script src="script.js"></script>
</body>
</html>
JAVASCRIPT
var app = angular.module('myApp', []);
app.controller('MyController', function($scope) {
$scope.brands = [{
name: 'Kewl',
type: 'Cereal'
}, {
name: 'Joku',
type: 'Toy'
}, {
name: 'Loko',
type: 'Couch'
}]
$scope.products = [{
name: 'Kewlio',
type: 'Sugar Cereal',
brand: 'Kewl'
}, {
name: 'Kewliano',
type: 'Healthy Cereal',
brand: 'Kewl'
}, {
name: 'Jokurino',
type: 'Rattle',
brand: 'Joku'
}, {
name: 'Lokonoko',
type: 'Recliner',
brand: 'Loko'
}, {
name: 'Lokoboko',
type: 'Love Seat',
brand: 'Loko'
}]
$scope.showType = function(item) {
this.show = !this.show;
}
$scope.filterProducts = function(brand) {
return function(value) {
if(brand) {
return value.brand === brand;
} else {
return true;
}
}
}
});
IMPORTANT NOTE: I realize I could add an attribute to the object (brand.show) and pass the object into the function, then change that attribute to true/false, but I don't want to do this because in my actual application, the button will show a form that edits the brand/product and submits the info to Firebase, and I don't want the object to have a 'show' attribute on it. I would rather not have to delete the 'show' attribute every time I want to edit the info in Firebase.
ng-repeat directive create own scope, when you do
this.show = !this.show
you create/change show property in current scope, if click brand button - for brand scope, that global for product, and when click in product button - for scope concrete product.
To avoid this, you should create this property before clicking button, for example with ng-init, like
ng-init="show=false;"
on element with `ng-repeat" directive
Sample
var app = angular.module('myApp', []);
app.controller('MyController', function($scope) {
$scope.brands = [{
name: 'Kewl',
type: 'Cereal'
}, {
name: 'Joku',
type: 'Toy'
}, {
name: 'Loko',
type: 'Couch'
}]
$scope.products = [{
name: 'Kewlio',
type: 'Sugar Cereal',
brand: 'Kewl'
}, {
name: 'Kewliano',
type: 'Healthy Cereal',
brand: 'Kewl'
}, {
name: 'Jokurino',
type: 'Rattle',
brand: 'Joku'
}, {
name: 'Lokonoko',
type: 'Recliner',
brand: 'Loko'
}, {
name: 'Lokoboko',
type: 'Love Seat',
brand: 'Loko'
}]
$scope.showType = function(item) {
this.show = !this.show;
}
$scope.filterProducts = function(brand) {
return function(value) {
if (brand) {
return value.brand === brand;
} else {
return true;
}
}
}
});
/* Styles go here */
h1 {
font-family: impact;
}
h2 {
font-family: arial;
color: blue;
margin-bottom: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.js"></script>
<div ng-app="myApp">
<div ng-controller="MyController as vm">
<div ng-repeat="brand in brands" ng-init="show=false">
<h1>
{{brand.name}}
</h1>
<button ng-click="showType(brand)">
Show Brand Type
</button>
<div ng-show="show">
{{brand.type}}
</div>
<div ng-repeat="product in products
| filter:filterProducts(brand.name)" ng-init="show=false">
<h2>
{{product.name}}
</h2>
<button ng-click="showType(product)">
Show Product Type
</button>
<div ng-show="show">
{{product.type}}
</div>
</div>
</div>
</div>
</div>
The easiest fix for this, if you don't mind putting temporary properties in your data is the following changes:
<div ng-show="product.show">
{{product.type}}
</div>
and
<div ng-show="brand.show">
{{brand.type}}
</div>
and then in your controller
$scope.showType = function(item) {
item.show = !item.show;
}
Alternatively, if you don't want to touch the object properties, you can create an $scope.shownTypes array and have your click either push the object into or remove the object from the shown array. THen you can check for the object's existence in the array and show or not show the type appropriately. Let me know if you need a sample of that.
Your show boolean attribute same for whole tree (is in same scope). Using angular directive with child scope scope:true in ng-repeat helps to isolate each show property. I have forked your plunker code:
http://plnkr.co/edit/cMSvyfeCQOnTKG8F4l55?p=preview

Categories

Resources