Checkbox with knockout data binding giving wrong value on checked event - checkbox

<input type="checkbox" data-bind="checked: appealsFromThisCase, event: { change: onappealsFromThisCaseChange}" id="appealsforCaseCheckBox"/>
vm.onappealsFromThisCaseChange = function () {
if (vm.appealsFromThisCase())
{
vm.predicate(new breeze.Predicate("CaseId", "==", caseID));
return datacontext.getCaseAppeals().then(function () {
return true;
});
}
}
else
return vm.getAppeals();
Above is a checkbox which is bound to appealsFromThisCase observable which is initially false.
On change event, onappealsFromThisCaseChange is fired and I see that the observable appealsFromThisCase gives true when checkbox is unchecked and false when the checkbox is checked.

It looks like ko handles the change event in a strange way. Fiddle: http://jsfiddle.net/wnLyV/1/
js:
var VM = function(){
var self = this;
self.appealsFromThisCase = ko.observable(false);
self.appealsFromThisCase.subscribe(function(value){
console.log("from subscribe: " + value);
})
self.onappealsFromThisCaseClick = function(data){
console.log("from click: " + data.appealsFromThisCase());
return true;
}
self.onappealsFromThisCaseChange = function(data){
console.log("from change: " + data.appealsFromThisCase());
return true;
}
}
ko.applyBindings(new VM());
Also see these:
knockout.js and listen to check event on checkbox
Knockout checkbox change event sends old value
Can you replace the change event binding with a click binding, or with a subscription, like in the fiddle, or do you specifically need change?
EDIT:
So, based on the comments below, the final answer is:
Wrap onappealsFromThisCaseChange function into another function, which will be the actual handler and always return true. So just make another handler like this:
function(){
onappealsFromThisCaseChange();
return true;
}

Related

Intercept checkbox change event in VueJS

I have a long set of checkboxes. I would like two groups of three of them to behave as radio buttons. Now, leaving aside my UX choices, how can I make this work?
The checkboxes are implemented as properties on a single object, layer:
data() {
return {
layer: {},
}
},
watch: {
layer: {
handler(val, oldval) {
mapping.updateLayers(val)
},
deep: true,
},
},
That works fine. But intercepting val and updating this.layer inside handler() doesn't:
handler: function(val, oldval) {
if (val.FutureYear) { this.layer.NextYear = false; this.layer.ExistingYear = false; }
if (val.ExistingYear) { this.layer.NextYear = false; this.layer.FutureYear = false; }
if (val.NextYear) { this.layer.ExistingYear = false; this.layer.FutureYear = false; }
mapping.updateFoiLayers(val);
},
How can I achieve this result? (I'd prefer not to have to implement actual radio buttons because that makes managing all the layers and UI more complex.)
Example: https://codepen.io/jacobgoh101/pen/NyRJLW?editors=0010
The main problem is the logic in watch.
If FutureYear is selected already, other field becomes unchangeable. Because if (val.FutureYear) is always the first one being triggered and the other 2 field will always be set to false immediately.
Another thing about watch is that it will be triggered when
user changed the value
program changed the value (this is unnecessary and make things harder to handle)
Therefore, handling #change event is more appropriate in this scenario.
JS
methods: {
handleChange: function(e) {
const name = e.target.name;
if (this.layer[name]) {
Object.keys(this.layer).map((key)=>{
if(key != name) {
this.layer[key] = false;
}
})
}
}
}
html
<div id="app">
<input type="checkbox"/ v-model="layer.FutureYear" name="FutureYear" #change="handleChange($event)">FutureYear<br/>
<input type="checkbox"/ v-model="layer.NextYear" name="NextYear" #change="handleChange($event)">NextYear<br/>
<input type="checkbox"/ v-model="layer.ExistingYear" name="ExistingYear" #change="handleChange($event)">ExistingYear<br/>
</div>

setState not updating when set to boolean

I have the following method in a react component:
handleCheckBoxClick() {
var checkbox = document.getElementById("boldCheckbox").checked;
this.setState({ischecked : checkbox});
if(this.state.ischecked) {
this.setState({weight:'bold'});
} else {
this.setState({weight:'normal'});
}
}
but if I change the if statement to:
if(checkbox) {
this.setState({weight:'bold'});
} else {
this.setState({weight:'normal'});
}
it works fine but I can't figure out why the first way doesn't work.
The method setState() is asynchronous, and updates are often batched together. In your case, ischecked is updated together with the weight, so when you set the weight you still refer to the old value.
One solution is to use setState()'s callback that will be called after the state is updated.
Note: to get the checkbox checked state, use the event object e passed to the handler instead of querying the DOM.
handleCheckBoxClick(e){
var checked = e.target.checked;
this.setState({ischecked : checked}, function() {
if(this.state.ischecked){
this.setState({weight:'bold'});
}else{
this.setState({weight:'normal'});
}
});
}
A better solution is to update both properties because you know if the checkbox is checked:
handleCheckBoxClick(e){
var checked = e.target.checked;
this.setState({
ischecked : checked,
weight: checked ? 'bold' : 'normal'
});
}
Thats cause this.setState({ isChecked : checkbox }); has not finished before you ask it in the if statement.

Toggling between $pristine and $dirty in AngularJS

I want to detect when a user has entered values into any form field by using the $dirty property and setting a flag accordingly. Not surprisingly, this works:
$scope.$watch('formDetails.$dirty', function() {
USR.userInputRecorded = true;
});
But I'd also like to detect when/if the user has emptied all fields and effectively restored the form to its original empty state. The snippet below does not work and I'm not sure why. Is there a way to watch for when the form changes back to "not dirty"?
$scope.$watch('formDetails.$pristine', function() {
USR.userInputRecorded = false;
});
Thanks.
Try this:
$scope.$watch('formDetails.$dirty', function(value) {
if (value === '') {
// field has been emptied;
your.form.$setPristine(true);
} else {
USR.userInputRecorded = true;
}
});

Marionette prevent region destroy

I am using Marionette region to display templates based on user radio input:(text/file).
Here is my itemview
var fileTemplateView = Marionette.ItemView.extend({
template : "#file-upload-template"
});
and region defined as
regions : {
composeRegion : "#compose-region",
}
and event declared as
events : {
"click #msg-input-type input:radio" : "changedRadio"
}
and event trigger function is
changedRadio : function(evt) {
var self = this;
var checkedObject = evt.currentTarget;
console.log('Radio Change Event'+checkedObject.value);
if (checkedObject.value === "file") {
if (self.fileView === undefined) {
self.fileView = new fileTemplateView();
}
this.composeRegion.show(self.fileView, { preventDestroy: true });
} else if (checkedObject.value === "text") {
if (self.textView === undefined) {
self.textView = new textTemplateView();
}
this.composeRegion.show(self.textView, { preventDestroy: true });
}
But preventDestroy method may not be working as defined where template is resetting on everytime radio event happen.
Your help is appreciated.
The preventDestroy option prevents the swapped view from being destroyed. This doesn't mean that is won't be re-rendered the next time it is shown. Make sure you are saving the state of the view so it can be used to reconstruct the view properly the next time.

Problems with ngChange for <select> after 1.3.0 rc0

I have been using a beta version of 1.3 and now after moving to 1.3.1 I notice a problem which by checking all earlier versions I see it appears to have started in 1.3.0 rc1.
I have code like this:
<select ng-model="home.modal.topicId"
ng-change="ctrl.modalTopicChanged()"
ng-options="item.id as item.name for item in home.modal.option.topics.data"
ng-required="true">
<option style="display: none;" value="">Select Topic</option>
</select>
Prior to rc1 the ng-change was not being fired when the form was first displayed. Now it is being fired with a home.modal.topicId of undefined. This is a breaking change for me but it's not mentioned in the breaking change section and I wonder if it's a bug that has yet to be noticed.
Here is the stack trace produced:
TypeError: Cannot read property 'dataMap' of undefined
at AdminProblemController.modalTopicChanged (http://127.0.0.1:17315/Content/app/admin/controllers/ProblemController.js:109:114)
at $parseFunctionCall (http://127.0.0.1:17315/Scripts/angular.js:11387:18)
at Scope.$get.Scope.$eval (http://127.0.0.1:17315/Scripts/angular.js:13276:28)
at http://127.0.0.1:17315/Scripts/angular.js:19888:13
at http://127.0.0.1:17315/Scripts/angular.js:19499:9
at forEach (http://127.0.0.1:17315/Scripts/angular.js:331:20)
at $$writeModelToScope (http://127.0.0.1:17315/Scripts/angular.js:19497:5)
at writeToModelIfNeeded (http://127.0.0.1:17315/Scripts/angular.js:19490:14)
at http://127.0.0.1:17315/Scripts/angular.js:19484:9
at validationDone (http://127.0.0.1:17315/Scripts/angular.js:19420:9)
What I notice here is a new function: writeToModelIfNeeded
When I look at the change log differences I cannot find any mention of this function being introduced when I check all the changes and the line numbers.
I would like to get some advice on this. Firstly is it possible to find the change that caused the addition of the writeToModelIfNeeded then secondly is this the correct functionality for the select box. I thought the whole idea was that the ng-change would only fire if the model value was defined.
For reference here's the area of new code that seems to have been added with 1.3.0 rc.1
**
* #ngdoc method
* #name ngModel.NgModelController#$commitViewValue
*
* #description
* Commit a pending update to the `$modelValue`.
*
* Updates may be pending by a debounced event or because the input is waiting for a some future
* event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
* usually handles calling this in response to input events.
*/
this.$commitViewValue = function() {
var viewValue = ctrl.$viewValue;
$timeout.cancel(pendingDebounce);
// If the view value has not changed then we should just exit, except in the case where there is
// a native validator on the element. In this case the validation state may have changed even though
// the viewValue has stayed empty.
if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
return;
}
ctrl.$$lastCommittedViewValue = viewValue;
// change to dirty
if (ctrl.$pristine) {
ctrl.$dirty = true;
ctrl.$pristine = false;
$animate.removeClass($element, PRISTINE_CLASS);
$animate.addClass($element, DIRTY_CLASS);
parentForm.$setDirty();
}
this.$$parseAndValidate();
};
this.$$parseAndValidate = function() {
var parserValid = true,
viewValue = ctrl.$$lastCommittedViewValue,
modelValue = viewValue;
for(var i = 0; i < ctrl.$parsers.length; i++) {
modelValue = ctrl.$parsers[i](modelValue);
if (isUndefined(modelValue)) {
parserValid = false;
break;
}
}
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
// ctrl.$modelValue has not been touched yet...
ctrl.$modelValue = ngModelGet();
}
var prevModelValue = ctrl.$modelValue;
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
if (allowInvalid) {
ctrl.$modelValue = modelValue;
writeToModelIfNeeded();
}
ctrl.$$runValidators(parserValid, modelValue, viewValue, function() {
if (!allowInvalid) {
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
writeToModelIfNeeded();
}
});
function writeToModelIfNeeded() {
if (ctrl.$modelValue !== prevModelValue) {
ctrl.$$writeModelToScope();
}
}
};
this.$$writeModelToScope = function() {
ngModelSet(ctrl.$modelValue);
forEach(ctrl.$viewChangeListeners, function(listener) {
try {
listener();
} catch(e) {
$exceptionHandler(e);
}
});
};
I was able to reproduce your issue by doing this. Without seeing your controller though not sure if same thing:
this.modal = {
topicId:null,
option:{
topics:{
data:[{id:1,name:'item1'},{id:2,name:'item2'}]
}
}
};
What is happening here is that angular says null is an invalid value so by default sets it to undefined. You can fix this by setting it to 'undefined' or adding this to your html:
ng-model-options="{allowInvalid:true}"
also tested Josep plunker and changing that value to null also caused ngChange to fire

Resources