Reputation: 795
I'm dynamically building a set of checkboxes. Clicking any of the checked boxes should uncheck the first (index wise) checked box. Clicking any of the unchecked boxes should check the last unchecked box.
I'm building the checkboxes using ng-repeat like this:
<input
type="checkbox"
ng-checked="values[$index]"
ng-repeat="n in values track by $index"
ng-click="click($event,$index)" />
And my controller looks like this:
.controller("myCtrl", function ($scope) {
$scope.values = [true,true,true,true];
$scope.click = function (event,n) {
event.preventDefault();
if ($scope.values[n] === true) {
$scope.values[$scope.values.indexOf(true)] = false;
} else {
$scope.values[$scope.values.lastIndexOf(false)] = true;
}
}
Here's a codepen of it all together http://codepen.io/anon/pen/rVLewJ
The problem that I'm running into is that using preventDefault seems to prevent ng-checked from updating the rendering of the box you click on (the others boxes re-render correctly). This causes the display to become out of sync with $scope.values.
Likewise, removing preventDefault doesn't prevent the box you're clicking on from changing its rendering, but (I believe because of ng-repeat's conservative re-rendering) ng-checked doesn't fire so it also gets out of sync.
I'm not using ng-changed because I'm specifically trying to prevent the checkboxes from changing if you're clicking on the "wrong" one. Regardless, I've tried using it instead of ng-clicked and it didn't fix anything.
I've tried using ng-model instead of ng-checked, but that seemed to prevent $scope.values from changing at all. Using $scope.$apply(), didn't help. Some of the things I've read have lead me to think I may need to use $watch
but I'm teaching myself Angular with this project so I'm not sure exactly how to apply that.
Nagasimha Iyengar provided a working solution here which I've simplified thusly
<input
type="checkbox"
ng-model="values[$index]"
ng-repeat="n in values track by $index"
ng-change="click($index)" />
Controller:
.controller("myCtrl", function ($scope) {
$scope.values = [true,true,true,true];
$scope.click = function (n) {
if ($scope.values[n] === true) {//clicking on unchecked box
$scope.values[n] = false;
$scope.values[$scope.values.lastIndexOf(false)] = true;
} else {//clicking on checked box
$scope.values[n] = true;
$scope.values[$scope.values.indexOf(true)] = false;
}
}
Codepen: http://codepen.io/anon/pen/zGBKxr
This solution is based on allowing the click event to happen, then undoing it before proceeding with the custom logic. It's certainly a simple solution but feels somewhat improper. Is it the most correct way of solving this problem?
Another small update. I dropped in ngTouch to try and make the app feel a bit quicker on mobile. At least in iOS Safari 8, ngTouch broke this solution. Still works fine on desktop, but the overridden ngClick prevents this solution form working. If you switch back to the original proposed logic it fixes iOS, but of course doesn't work on the desktop. I feel like this confirms my suspicion that the solution was not the correct one.
Upvotes: 2
Views: 2876
Reputation: 461
Here it is - the logic was reversed. And I used $apply with a timeout. Modified codepen: http://codepen.io/nagasimhai/pen/VLjjPe
angular
.module("myApp", [])
.controller("myCtrl", function ($scope) {
$scope.values = [true,true,true,true];
$scope.click = function (event,n) {
//event.preventDefault();
//console.log("b", n, $scope.values,$scope.values.indexOf(true), $scope.values.lastIndexOf(false));
if ($scope.values[n] === true) {//clicking on unchecked box
$scope.values[n] = false;
$scope.values[$scope.values.lastIndexOf(false)] = true;
//console.log("11");
} else {//clicking on checked box
$scope.values[n] = true;
$scope.values[$scope.values.indexOf(true)] = false;
//console.log('22');
}
//console.log("a",n, $scope.values);
setTimeout(function () {
$scope.$apply(function () {
});
}, 2000);
}
});
Upvotes: 1
Reputation: 1060
Your answer was a bit confusing but I believe I'm on the correct path. The problem. What you want to use is event.stopPropagation() to stop the event from propagating. In other words, you only want the event to apply to the specific element. If you use event.preventDefault() it cancels the event if cancelable, without stopping further propagation of the event.
angular
.module("myApp", [])
.controller("myCtrl", function ($scope) {
$scope.values = [true,true,true,true];
$scope.click = function (event,n) {
event.stopPropagation();
if ($scope.values[n] === true) {
$scope.values[$scope.values.indexOf(true)] = false;
$scope.values[n] = false;
} else {
$scope.values[$scope.values.lastIndexOf(false)] = true;
}
}
});
Try the code out at the codepen http://codepen.io/anon/pen/bdepPW
Upvotes: 0