AngularJS checkbox ng-checked not rendering if preventDefault used - javascript

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.
Update
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.

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);
}
});

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

Related

ng-change doesn't fire again after I change it's value

I have this in my HTML. Ignore any inline style, I'm testing,
<label class="toggle" style="float: right;">
<input id="check" type="checkbox" ng-model="check" ng-change="funCheck(check)">
<div class="track">
<div class="handle"></div>
</div>
</label>
<div ng-init="fromClock='01:00'; toClock='03:30';">
<clock-editor from="fromClock" to="toClock"
on-change="fromClock = from; toClock = to; funClock(from, to);">
</clock-editor>
<strong>{{fromClock}}</strong>
<strong>{{toClock}}</strong>
</div>
That's a toggle radio button and a clock.
Then I have these two functions in my controller:
$scope.funCheck = function(check) {
alert(check);
};
$scope.funClock = function(f_from, f_to) {
console.log(f_from + "---" +f_to)
$scope.check = false;
}
};
When the toggle is turned on I send the time from the clock somewhere. This works alright. However, what I want to do is uncheck the toggle if the time was changed.
I can do that with document.getElementById('check').checked = false; and the toggle moves back, but the ng-change on that radio won't fire again until I double check it. Like the value didn't change even if I can see how it's turned off visually.
ng-model does not work on strong element and there is no ng-change event for strong element. So basically you can achieve this by using two watch variables like this, Hope this will help you.
$watch(function(){
return $scope.fromClock;
}, function() {
$scope.funCheck();
})
$watch(function(){
return $scope.toClock;
}, function() {
$scope.funCheck();
})
The whole point of using angular is the fact that you shouldn't have to edit the DOM manually like you're doing.
Change this part of your code which unchecks the checkbox
$scope.funClock = function(f_from, f_to) {
if (document.getElementById('check').checked) {
document.getElementById('check').checked = false;
}
};
to
$scope.funClock = function(f_from, f_to) {
if($scope.check){
$scope.check = false;
}
};
Also, you shouldn't care about checking if it's already checked or not as if you set the checked to false and it's already false there will be no change so just remove the if statement completely.
Edit
Seems like ng-change will only fire if there is a change on the input itself and not if that change has happened programmatically, so there are two ways to do this.
Call the change function inside of the funClock.
This would be the code for that
$scope.funClock = function(f_from, f_to) {
if($scope.check){
$scope.check = false;
funCheck($scope.check);
}
};
Add a watch for check.
Or the code for the watch
$scope.$watch('check', function(newValue, oldValue) {
if (newValue != oldValue) {
funCheck(newValue);
}
});
In angularjs , we cann't directly change elements value. we need to
use $compile . First include it in controller and then make use of
it like wise -
var list = '<input id="check" type="checkbox" ng-model="check" checked ng-change="funCheck(check)">';
var selctr = $("#selector");
var ele = angular.element(list);
compiled = $compile(ele);
selctr.html(ele);
compiled($scope);

Avoid multiple clicks on button in AngularJS

I have a button which on click will save the form information.
The problem is, user instead of clicking 1's on the "Save" button clicks on it multiple times as long as it disappears on the screen. With this, I am saving same form which inturn throw duplicate exceptions.
Please help me. Thanks in advance.
<button ng-click="myFunc()">Save</button>
In above code, myFunc() is triggered with the number of times user clicks.
Use a $scope variable and ng-disabled to update the click and check for the variable,
<button ng-click="myFunc()" ng-disabled="buttonClicked"></button>
Controller
$scope.buttonClicked = true;
I think disable the button after the first click will help you to solve this issue.
That's in case you want this feature related to multiple buttons:
JS:
$scope.disabled = {};
$scope.myFunc = function(identifier) {
$scope.disabled[identifier] = 1;
...
}
HTML:
<button ng-click="myFunc(identifier)" ng-disabled="disabled.identifier"></button>
In case that you want this feature only on one button:
JS:
$scope.disabled = 0;
$scope.myFunc = function() {
$scope.disabled = 1;
...
}
HTML
<button ng-click="myFunc()" ng-disabled="disabled"></button>
You can disable your submit button after the first click to prevent duplicate entry.
For Example, you have HTML something like,
<div ng-app="mydemo" ng-controller="myController">
<button type="submit" ng-disabled="isDisabled" ng-click="myFunc()"> Submit</button>
</div>
angular.module('mydemo', [])
.controller('myController',function($scope){
$scope.isDisabled = false;
$scope.disableButton = function() {
$scope.isDisabled = true; // To disable Button
}
});
This way you can disable button. It will surely work for you. Thanks.
create a directive for this, so that it can be reused
app.directive('clickAndDisable', function() {
return {
scope: {
clickAndDisable: '&'
},
link: function(scope, iElement, iAttrs) {
iElement.bind('click', function() {
iElement.prop('disabled',true);
scope.clickAndDisable().finally(function() {
iElement.prop('disabled',false);
})
});
}
};
});
so use click-and-disable="myFunc()" rather than ng-click
Hiding the form after first click resolved this issue.

Prevent inner div from triggering outer-div events

I created a basic row click-and-drag selection function. However the issue is click-and-dragging over the columns toggles them on and off. I am looking for some sort of return false or stopPropagation of some sort but I cannot get these to work.
return false does help by preventing text selection, but it does not prevent column selection from affecting row selection.
To demonstrate, http://jsfiddle.net/sjwcztre/, try to select the rows from the right side - no problem. But try to select the rows where column text is present - it goes wonky
var isMouseDown = false;
$('.row').each(function () {
$(this).mousedown(function () {
isMouseDown = true;
rowClickHandler(this);
console.log('mdown');
return false;
})
.mouseover(function () {
if (isMouseDown) rowClickHandler(this);
});
$(document).mouseup(function () {
isMouseDown = false;
});
});
function rowClickHandler(obj) {
$(obj).toggleClass('highlight');
}
Check this, see if it works for you. Basically you need to create events for the children of your rows and handle them differently, adding a hoverChild control variable helped to prevent the hover from the parent activating once again.

ngModelCtrl - $setViewValue without $setDirty();

I have a directive which manipulates the $viewValue of an input field via ngModelCtrl.$setViewValue() on blur.
function _onFocus () {
presentation = false;
if(!ctrl.$isEmpty(ctrl.$modelValue)) {
ctrl.$setViewValue(String(ctrl.$modelValue).replace(/\./, ','));
ctrl.$render();
}
}
Plunkr
Since this is only an appearance thing, I wonder if there is the possibility of setting the ViewValue without altering the $pristine/$dirty state of the input field itself.
Reference Angular: $setDirty on $commitViewValue
I would probably set the value of the element instead of setting view value and rending all over, since it is just the appearance thing that happens on manually registered event, and you do not have to play with the ngmodel's validation properties.
i.e:
function _onBlur () {
//....
$element.val(_formatCurrency(ctrl.$modelValue));
}
function _onFocus () {
//...
$element.val(String(ctrl.$modelValue).replace(/\./, ','));
}
Plnkr

Fixing toggle all behavior in checkbox element

I'm trying to toggle all checkboxes on a table and my code works but has a few issues and I don't find how to get ride of them. So here is the code:
$(function () {
$('#toggleCheckbox').on('click', function () {
var $toggle = $(this).is(':checked');
$("#codigoArancelarioBody").find("input:checkbox").click();
});
});
Take a look at this Fiddle I setup for testing and do this tests:
Mark the first checkbox (the one at table heading level) the rest of them inside #codigoArancelarioBody get checked and this is right
Mark first the checkbox at the first row (the only at table body level) and then mark the toggleAll you will see how things goes wrong since if I check the toggleAll them all should remain checked and that's the wrong part on my code
How I can fix this? Also I'll like to add a class 'removedAlert' to those TR I mark, how?
You need two click event handlers, one for the check/uncheck all box and one for the other ones
JS
$('#toggleCheckbox').on('click', function () {
var $toggle = $(this).is(':checked');
$("#codigoArancelarioBody").find("input:checkbox").prop("checked", $toggle);
});
$("#codigoArancelarioBody input:checkbox").on('click', function () {
if (!$(this).is(':checked')) {
$('#toggleCheckbox').prop("checked", false);
} else if ($("#codigoArancelarioBody input:checkbox").length == $("#codigoArancelarioBody input:checkbox:checked").length) {
$('#toggleCheckbox').prop("checked", true);
}
});
DEMO
since the same code will be applied in a lot of places on my code and
to avoid DRY, I'll like to pass the selector as a parameter in all
your code solution could you edit your post to achieve this?
$toggleCheckBox = $('#toggleCheckbox');
$checkBoxTbody = $("#codigoArancelarioBody");
$toggleCheckBox.on('click', function () {
var $toggle = $(this).is(':checked');
$checkBoxTbody.find("input:checkbox").prop("checked", $toggle);
});
$checkBoxTbody.find("input:checkbox").on('click', function () {
if (!$(this).is(':checked')) {
$toggleCheckBox.prop("checked", false);
} else if ($checkBoxTbody.find("input:checkbox").length == $checkBoxTbody.find("input:checkbox:checked").length) {
$toggleCheckBox.prop("checked", true);
}
});
DEMO
If you don't need the "click" event for something else you can do this:
$(function () {
$('#toggleCheckbox').on('change', function () {
var toggle = $(this).is(':checked');
$("#codigoArancelarioBody").find("input:checkbox").prop('checked', toggle ).closest('tr').addClass('removedAlert');
});
});
The code is actually executing what you told it to do, i.e. every time I click the checkbox on top click to other checkboxes. This way if a box is checked it will uncheck itself, because it won't mind if the top is checked or not.
What you really want is "when I check the box on top, check all the others, when I uncheck it, then uncheck all the others", which is sort of different as you see.
Try this:
$(function () {
// best practice: always store the selectors you access multiple times
var checkboxes = $("#codigoArancelarioBody").find("input:checkbox"),
toggleAll = $('#toggleCheckbox');
toggleAll.on('click', function () {
// is the top checkbox checked? return true, is it unchecked? Then return false.
var $toggle = $(this).is(':checked');
// .prop('checked', true) makes it checked, .prop('checked', false) makes it unchecked
checkboxes.prop('checked', $toggle);
});
});
see: http://jsfiddle.net/gleezer/nnfg80x1/3/

Categories