How to update ng-class based on ng-change? - angularjs

I have an ng-repeat that creates several widgets, each with it's own select dropdown.
<div ng-repeat="item in widget.items" class="col-md-6">
<select ng-model="item.chosenTag"
ng-change="widget.updateTag(item)"
ng-class="widget.getButtonClass(item.tag, item.id)">
<option value="companies"
ng-selected="{{item.tag == 'companies'}}"
changed="companies">companies</option>
<option value="news"
ng-selected="{{item.tag == 'news'}}"
changed="news">news</option>
<option value="people"
ng-selected="{{item.tag == 'people'}}"
changed="people">people</option>
</select>
</div>
As the view is loaded, the ng-class="widget.getButtonClass(item.tag, item.id)" calls a function to check if 2 values are met, then sets ng-class to either btn-success or btn-default.
// This checks the view after load to change the class on each widget select
var vm = this;
vm.getButtonClass = function(tag, id) {
console.log('in getButtonClass');
if (tag && id) {
return 'btn-success';
} else {
return 'btn-default';
}
};
Next if the user now updates a select within any widget, ng-change will update to the correct value.
However I need to somehow re-run the ng-class check or rather just go ahead and add btn-success to the select without having to refresh the page.
How would you go about accomplishing this?
Is there a way to do something like this?
ng-class="{ functionToUpdateAll | boolean based on ng-change }"

Is there any specific reason that you need to call the function? Why dont you bind directly to those variables?
ng-class="'btn-success':item.tag && item.id, 'btn-default': !(item.tag && item.id)"
In that way you take advantage of mutual binding i.e. whenever the item.tag or item.id are changed, the class will be evaluated accordingly.

I suggest that instead of calling the function in ng-class, just assign it to a variable:
<select ng-model="item.chosenTag"
ng-change="widget.updateTag(item)"
ng-class="mySelectClass">
And in your updateTag function in the controller update that variable:
vm.updateTag = function(item) {
if (item.tag && item.id) {
vm.mySelectClass = 'btn-success';
} else {
vm.mySelectClass = 'btn-default';
}
};

Related

Remove empty option in the select using ng-model

I am new to angular js. In my code user changes the value of radio buttons. And depending on the value of the selected radio button, a piece of code is loaded from the ng-switch
HTML:
<body ng-app="">
<div ng-repeat="button in modes">
<label>
<input type="radio" ng-model="data.mode" value="{{button.value}}" ng-click="clearObjectIdModal()" name="e_modes">
{button.label}}
</label>
</div>
<div ng-switch on="data.mode">
<div ng-switch-when="client">
<label for="e_selected_object_item_id">Select Client name: </label>
<select id="e_selected_object_item_id" name="e_selected_object_item_id" ng-model="currentDataItem.object_id" required>
<option ng-repeat="item in customersListArr" value="{{ item.id }}">{{ item.Name }}</option>
</select>
</div>
<div ng-switch-when="agent">
// This part is similar to the previous one
</div>
</div>
</body>
Controller part:
$scope.data = {};
$scope.setFile = function () {
if ($scope.data.mode == 'client')
return 'client';
else if ($scope.data.mode == 'agent')
return 'agent';
$scope.modes = [{
value: 'client',
label: 'Client'
},{
value: 'agent',
label: 'Agent'
}];
$scope.currentDataItem = data; // data is preloaded from inputs in form
There is also a ng-click="clearObjectIdModal()" that clears the model when switching radio buttons:
$scope.clearObjectIdModal = function() {
$scope.currentDataItem = "";
}
The problem is that every time when the radio button is switched to the select value, which dynamically changes, the value of the first option in it becomes equal to undefined. Because in the array from where these options are built there is no such object_id (This is the id that is not there, so an empty field is drawn).
That is, there are all works. But the first option in the select(after switching to another radio button) is rendered as an empty string.
There are thoughts, how it can be fixed?
I'm not sure if I understand you problem correctly but I would suggest a few improvements.
change your setFile function to as follows
$scope.setFile = function (){return $scope.data.mode;}
I also do not see the closing brackets for your function in your code. Besides if your function will only return the data.mode then why need the function?
I would suggest initialize your data object properly like:
$scope.data = {mode:'client'};
Change your clearObjectIdModal function as:
$scope.clearObjectIdModal = function(mode)
{
$scope.currentDataItem = "";
$scope.data.mode=mode;
}
and in your HTML use it as ng-click="clearObjectIdModal(button.mode)"
So in function clearObjectIdModal() I wrote:
$scope.clearObjectIdModal = function() {
if ($scope.e_data["mode"] == 'client') {
if ($scope.customersListArr.length > 0) {
$scope.currentDataItem.object_id = $scope.customersListArr[0]['id'];
}
}
else if ($scope.e_data["mode"] == 'agent') {
if ($scope.agentsListArr.length > 0) {
$scope.currentDataItem.object_id = $scope.agentsListArr[0]['id'];
}
}
}
And after this when I change radio buttons the first option in current select(which every time is changed) will be not empty.
Also the problem with an additional empty option is possible to solve when you add a title as the first item in the list:
<option value="" disabled>Select</option>

is it possible to use a variable filter?

I'm trying to use filter who gonna change depending on a click or write on an input.
<input
name="hotelinput"
type="text"
ng-keydown="changeFilterToKeyPressed()"
ng-click="changeFilterToClicked()">
<div ng-repeat="hotel in filteredHotels = (hotels | VARIABLEFILTER | orderBy: 'country') track by $index">
...
</div>
I know that you can do filter:variable and change it in the controller but I need to change the full filter for one of my custom filters every time.
I didn't tested it but something like this could be possible
JS
if(x){
$scope.VARIABLEFILTER = $filter('myCustomFilter')
} else {
$scope.VARIABLEFILTER = $filter('myCustomFilter2')
}
I made it to work and rewrote the filter I once did in other question
http://plnkr.co/edit/LuA3hYr7mImihYnVIxqQ
.filter('applyFilter', function($filter) {
return function(input, filterToApply) {
return filterToApply === undefined ? input : $filter(filterToApply)(input)
}
})
I hope that's what you were looking for
You need ngChange instead of ngClick
<input
name="hotelinput"
type="text"
ng-model="filterKey"
ng-change="changeFilterToKeyPressed()"
ng-focus="changeFilterToClicked()">
and update your function changeFilterToKeyPressed
$scope.changeFilterToKeyPressed = function() {
$scope.VARIABLEFILTER = someUpdatesWhichYouWant($scope.filterKey); //You can use filterKey on any change
}

TR element triggers checkbox via ng-click, but ng-change on checkbox will not fire

I have successfully created functionality to check a hidden checkbox on the ng-click of the row that the checkbox exists in that is generated with an ng-repeat. However, I also have functionality that adds that item to a separate array when the checkbox is checked using the ng-change directive.
My code works perfectly if I un-hide the element and check the checkbox directly, but if I use the ng-click directive on the TR, the checkbox gets checked (visibly) but the array isn't updated. If I then click on it again, it remains checked and the item is added to the new array.
This isn't an issue where the ng-click is taking two clicks to fire. Here is my markup:
<tr ng-cloak ng-repeat="item in mostRecent | orderBy:sortType:sortReverse | filter: query" class="hovered" ng-class="{'hide':showReports && item.status == 'Not Started' || showReports && item.status == 'inProgress','rep-checked': bool}" ng-click="bool = !bool">
<td class="hidden"><div class="checkbox">
<label class="checkbox">
<input type="checkbox" ng-change="sync(bool, item)" ng-model="bool" ng-checked="isChecked(item)">
</label>
</div></td>
and the js:
$scope.isChecked = function(id) {
var match = false;
for(var i=0 ; i < $scope.selectionData.length; i++) {
if($scope.selectionData[i].id == id){
match = true;
}
}
return match;
};
// Create a new array from the selected data
$scope.selectionData = [];
var selectionData = $scope.selectionData;
var result = selectionData.filter(function(e){ return e.id == id; });
$scope.sync = function(bool, item){
if ($scope.selectionData) {
$scope.selectionData.push(item);
}
else {
$scope.selectionData.splice(item);
}
console.log('check' + $scope.selectionData);
};
After some deeper research, I found that the ng-click function on the input isn't registering because the ng-click function of the tr element isn't actually emulating a click event on the input. I added a dynamic ID and replaced the ng-click function with a javascript function of
getElementById('checkboxID').click();
And it worked as expected.

using AND operator in Angular

I have the following HTML
<input type="checkbox" id="cbxSelectAllPreventive"/> <label>Select All</label>
<ul style="list-style: none;">
<li ng-repeat="test in lists.Tests">
</li>
</ul>
Test is an array of complex objects having isSelected property.
I want to use the checkbox as SelectAll functionality.
To do this , I need to supply ng-model to the checkbox, I can supply it as a method which checks in each of the tests and returns true/false. Is there any way to do this inline, without writing a method ?
I only see a way to do it using a function on ng-change
<input type="checkbox" id="cbxSelectAllPreventive"
ng-model="checked" ng-change="setAll(checked)"/>
...
$scope.setAll = function(isSelected){
$scope.lists.Tests.map(function(el){
el.isSelected = isSelected;
})
}
Working fiddle
EDIT:
For complete two-way connection between items' selection and check box the code will be a bit more complicated.
We will use an extra variable in $scope to reflect the label of check box. Don't forget to init the variable at controller creation
<label for="cbxSelectAllPreventive">{{selectLabel}}</label>
...
$scope.selectLabel = 'Select All';
setAll function should take care of setting this variable.
$scope.setAll = function(isSelected){
$scope.selectLabel = isSelected ? 'Deselect All' : 'Select All';
$scope.lists.Tests.map(function(el){
el.isSelected = isSelected;
})
}
And finally you definitely will have an option to select/deselect individual items. For this case you have to $watch your list. Mind the third parameter true which does deep comparison otherwise it won't "notice" changes inside objects.
$scope.$watch('lists.Tests', function(){
var text = $scope.lists.Tests.map(function(el){ return el.isSelected; }).join();
console.log(text);
var allSelected = $scope.lists.Tests.every(function(el){ return el.isSelected;});
var noneSelected = $scope.lists.Tests.every(function(el){ return !el.isSelected;});
if(noneSelected){
$scope.selectLabel = 'Select All';
$scope.checked = false;
}else if(allSelected){
$scope.selectLabel = 'Deselect All';
$scope.checked = true;
}
if(!noneSelected && !allSelected) {
$scope.selectLabel = 'Undetermined';
// here set the check box to undetermined (third) state
}
}, true);
Updated fiddle
Did you try ng-checkbox is should do exaclty what

How can I validate an ng-repeated dropdown category selector?

I created a nested category model. In order to select a category for a product, you see a dropdown with the main categories. When you select one, a new dropdown is added to the right with the child categories to the one you selected. This repeats until you reach the last level. When this happens $scope.product.category_id is set. I want to invalidate the whole set of fields when $scope.product.category_id is null.
I've been using angular-bootstrap-show-errors for validation and I tried to mix it with ui-utils to achieve this one, using a custom function: validate_category().
Here's the HTML I used:
<span ng-repeat="parent_id in category_dropdown_parents" show-errors="{ skipFormGroupCheck: true }">
<select class="form-control" name="category_id"
ng-init="selected_category[$index] = init_select($index);"
ng-model="selected_category[$index]"
ng-options="category.name for category in (categories | filter : { parent_id: parent_id } : true ) track by category.id "
ng-change="select_category(selected_category[$index], $index)"
ui-validate="'validate_category()'" // added this to work with ui-utils
>
</select>
<span ng-if="$index+1 != category_dropdown_parents.length">/</span>
</span>
And this is my simple validation function:
$scope.validate_category = function() {
return ( $scope.product.category_id !== null
&& $scope.product.category_id !== undefined);
}
But this is not working. Ideas?
EDIT: I just realized, that the problem with this is that the validation function on ui-validate is executed after the ng-change function, so it's never able to check the $scope.product.category_id update.
Your select is using
ng-model="selected_category[$index]"
but the validation function is using
$scope.product.category_id
Shouldn't it be using
ui-validate="'validate_category($index)'"
and
$scope.validate_category = function($index) {
return($scope.selected_category[$index] !== null
&& $scope.selected_category[$index] !== undefined);
}
This is not the ideal answer but it's the best I could get. Shame on me, this was too simple:
<select class="form-control" name="category_id"
ng-init="selected_category[$index] = init_select($index);"
ng-model="selected_category[$index]"
ng-options="category.name for category in (categories | filter : { parent_id: parent_id } : true ) track by category.id "
ng-change="select_category(selected_category[$index], $index)"
required // !!!
>
</select>
That's it, just added the required attribute. The problem with this is that since the I'm not validating product.category_id but just validating all the dropdowns to be not empty. I guess I
'll have to rely on the code on select_category().

Resources