Update on blur the first time only - javascript

I can defer model updating until blur with ng-model-options="{ updateOn: 'blur' }". This prevents annoying the user with changing validation states while entering input the first time around. However, when a user returns to a failed validation to revise it, the updateOn: 'blur' prevents the validation state from being updated when the user has fixed it (but before they leave the field).
How can I reset the updateOn option to allow the default model update schedule after one blur?

ngModelOptions directive $evals its value, but doesn't observe it for changes. In other words, you cannot achieve what you are looking for with something like the following:
<input ng-model="foo" ng-model-options="fooOptions">
and then change it in the controller:
$scope.fooOptions = {updateOn: "blur"};
$scope.changeOptions = function(){
$scope.fooOptions = {updateOn: "default"};
}
For simple cases, you could add another variable that changes how you show validation messages:
<form name="form1">
<input name="foo"
ng-model="foo" ng-model-options="{updateOn: 'default'}"
ng-blur="startShowingErrors = true" minlength="3">
<span ng-show="startShowingErrors && form1.foo.$invalid">invalid entry</span>
</form>
But, if you want the actual form state not update, then you'd need another directive as a facade to ngModelOptions to cause a $compile on every change.

Related

Triggering AngularJS form validation with javascript chrome extension

I am struggling to figure out a way to trigger these AngularJS classes on a form I am trying to automatically fill with a chrome extension I am making. The form (specifically a textbox) has to be validated/modified before it will be validated and therefore submitted.
I originally tried using javascript to set the value of the textbox using the value property. This did not validate the form. I then tried using a dispatch event to send a key to the textbox, which resulted in nothing being input into the text box. How can I validate the form without requiring human input, or is this not possible?
Clarification, I am trying to replicate this action without user input by using a chrome extension.
Reference https://www.w3schools.com/angular/angular_validation.asp
Sounds like you need to create some events to simulate whatever angular is listening for, probably change or blur. Here's an example using click from mozilla:
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events#Triggering_built-in_events
function simulateClick() {
var event = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
var cb = document.getElementById('checkbox');
var cancelled = !cb.dispatchEvent(event);
if (cancelled) {
// A handler called preventDefault.
alert("cancelled");
} else {
// None of the handlers called preventDefault.
alert("not cancelled");
}
}
How can I validate the form without requiring human input
Get the forms controls:
var controls = $scope.tdForm.$getControls();
Trigger their validators:
controls.forEach( _ => _.$validate() );
From the Docs:
$validate();
Runs each of the registered validators (first synchronous validators and then asynchronous validators). If the validity changes to invalid, the model will be set to undefined, unless ngModelOptions.allowInvalid is true. If the validity changes to valid, it will set the model to the last available valid $modelValue, i.e. either the last parsed value or the last value set from the scope.
For more information, see
AngularJS Form Controller API Reference
AngularJS ngModelController API Reference
When you type into the form, it updates the state of its controls (touched, dirty, etc.). According to how you define your fields validators (required, minLength...) the form will be valid or not after the user input.
In your submit method you should not proceed if any form fields are not valid. See AngularJS Developer Guide — Forms or Scotch Tutorials — AngularJS Form Validation you can have more details about AngularJS validation.
As Mike mentioned, you can use ngClass conditionally (see below) to apply some style classes only if a boolean condition occurr, for example the form is not valid.
<div ng-controller="ExampleController">
<form name="form" novalidate class="css-form">
<input type="text" ng-model="user.name" name="username" ng-class="{ 'error': !isValid }"/>
<div ng-show="form.$submitted>
<span ng-show="form.username.$error">Wrong Name</span></span>
</div>
<button ng-click="submit(user)"> Submit </button>
</form>
</div>
angular.module('formExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.isValid = true;
$scope.submit= function(user) {
if (user.name != 'Carl') {
$scope.isValid = false;
}
};
}]);
You can always programmatically change the form states if needed. For example to set the field to pristine:
$scope.form.$setPristine();
$scope.form.$setUntouched();
$setPristine sets the form's $pristine state to true, the $dirty state to false, removes the ng-dirty class and adds the ng-pristine class.
Additionally, it sets the $submitted state to false. This method will also propagate to all the controls contained in this form.
$setUntouched sets the form to its untouched state. This method can be called to remove the 'ng-touched' class and set the form controls to their untouched state (ng-untouched class).
Setting a form controls back to their untouched state is often useful when setting the form back to its pristine state.
UPDATE
Now it is clear what you are attempting to achieve. The two methods above can be used to set the form state, but if you want to validate it from code (this can be done passing the form to a service or directly in the controller for instance) then $validate() method will allow you to achieve that as mentioned by George.

How to wait before evaluating an input field

I've got a field for a product that its quantity is dependant on another product's quantity (cant be less than 70%, or more than 100%). Thing is, it evaluates it so quiclky that if the main field is '100', I cant enter 75 on the other field, because I first need to enter the '7', and my code considers it less than 70% and instantly changes it to the 70% value.
I've already tried using a self-made 'sleep' function, that makes a promise take some time to resolve.
setInterval and setTimeout also do not work as I intend for some reason (only evaluates the dependent field when I press enter, and it is not after the stablished time). This is not consistent with the rest of the table, so it is not a suitable solution.
This is the angular bit that controls this input
<div class="input-field">
<input class="input" type="number" [integerInput] ="true"
[disabled] ="item.deshabilitado( ) || !editable"
[(ngModel)] ="item.cantidad"
[ngModelOptions]="{standalone: true}"
(keyup) ="setCantidad( item, $event.target.value )"
max="9999" min="1" value="1" >
</div>
Sadly I cant get a minimal and working example. I need the dependent field to be able to evaluate its value automatically (without pressing enter or clicking on another field) without automatically correcting my input when I press only one character.
Use blur() method instead of keyup(). I guess you are validating the input with keyup() and each time you enter value it validates. For instance you are trying to enter 70 but when you enter first character, 7 it is invalid. The blur() fires your method and validates your input when you are done with inputting value.
<div class="input-field">
<input class="input" type="number" [integerInput]="true"
[disabled]="item.deshabilitado( ) || !editable"
[(ngModel)]="item.cantidad"
[ngModelOptions]="{standalone: true}"
(focusout)="setCantidad( item, $event.target.value )"
max="9999" min="1" value="1" >
</div>
In addition, you can use keyup.enter if a user is done with inputting value and presses enter. The value updates when the enter key is pressed or clicked somewhere else.
UPDATE:
I was able to solve this and get it to work like I intended at the beginning. The resulting code for the field I tried to validate would be
debounce (afterValueChanged)="setCantidad( item )"
It behaves in a way that does not need me to click outside the field for it to start validating, instead of the (blur) or (focusout)
Try to use the debounce function
Import debounceTime from rxjs and use it to add some delay :)
Check out this example
In the other field where you are trying to enter 75, you can add an ngModelOptions configuration to update only on blur.
[ngModelOptions]="{ updateOn: 'blur' }"
By default it updates on 'change', which explains the current behavior.
Update
You could also debounce the input using RXJS. This is probably closer to what you were trying to do with setInterval / setTimeout. This is also a use case where Reactive Forms really shines over Template Driven Forms imo, since you'll need to use RXJS operators.
For reactive driven forms, you just pipe an operator before subscribing to the valueChanges observable of the formControl
this.myInput.valueChanges
.pipe(
debounceTime(1000), // add 1 second of delay
distinctUntilChanged() // optional but recommended - only trigger for new values
)
.subscribe(val=> {
console.log('value', val);
});
It is possible to accomplish the same behavior in template driven forms but there is some setup involved. See this SO question/answer
As per the information given, you might want to debounce your function call.
Debounced functions do not execute when invoked, they wait for a pause of invocations over a configurable duration before executing; each new invocation restarts the timer.
<div class="input-field">
<input class="input" type="number" [integerInput] ="true"
[disabled] ="item.deshabilitado( ) || !editable"
[ngModel] ="item.cantidad"
[ngModelOptions]="{standalone: true}"
(keyup) ="setCantidad( item, $event.target.value )"
max="9999" min="1" value="1" >
</div>
In your component class:
timer;
setCantidad(item,value){
if(this.timer){
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
this.item.cantidad = value;
//perform any operations here
console.log(item, value);
},1000);
}
This will wait for the user to stop typing and execute the operation after the specified time in the timeout.
Note: One additional change, if you want to render the value in the same input field where you are typing, consider changing the [(ngModel)] -> [ngModel].
This will just perform a property binding and not event binding.
Demo stackblitz link : https://stackblitz.com/edit/angular-cu9gss

Serverside validation of AngularJS forms, use $setValidity in the view controller?

Or put in tl;dr code:
form.email.$setValidity('conflict', false);
is too sticky for my simple serverside validation flow.
I'm trying to get the form to show good feedback in the event that the user enters an email address already in use by another customer. I'm running AngularJS v1.2 and have this template:
<form name="form">
<input name="email" type="email" ng-model="..." required>
</form>
<div ng-messages="form.email.$error">
<div ng-message="conflict">Email address already in use.</div>
</div>
In my controller, I'll handle the submit event and trigger the validation in my $http.post().error handler like this:
$http.post('api/form/submit/path/here').error(function(resp) {
if (resp.details === 'conflict')
$scope.form.email.$setValidity('conflict', false);
});
The problem is that when the user goes back and changes the value in the input field, the error message doesn't go away. It sticks around until I manually call $scope.form.setValidity();.
The docs say implement a custom directive with an ng-model dependency, but that seems super overkill for my purposes. I've also tried setting $scope.form.email.$valid = false; and $scope.form.email.$invalid = true; but those don't change the appearance of the textbox.
Nothing in your code modifies the conflict validation key, except for when $setValidity('conflict', false) is explicitly called. Since that is the only code setting the state of the conflict validation key and there is nothing else resetting it to true, it's expected behaviour that editing the textbox wouldn't reset its conflict validation state.
To get the behaviour that you want, you need to code for it. One way is to use ng-change.
<input name="email" type="email" ng-model="..." required ng-change="resetConflictState()">
$scope.resetConflictState = function() {
$scope.form.email.$setValidity('conflict', true);
}

The trouble with $scope

I have an input file element within an angular view/form. I'm using ng-upload like this:
<input id="img" type="file" name="image" onchange="angular.element(this).scope().setFile(this)">
<input id="imgname" type="hidden" value=""></div>
Since I can't tell angular to listen for changes on input[type="file"] element, I've created the method that updates the hidden input that just holds the current filename. That way I can run my validator on the second field.
Another field I have has some sort of validator, like this:
<input ng-model="other" ng-change="chg()"/>
Now, the trouble is, if I trigger the validator, $scope.chg(), from setFile() method, I think I don't get the same scope - chg() runs, but it's as if the validator is in another scope and doesn't set my actual submit button to enabled. I tried logging from the chg() - it shows different scope then what I actually see on the view.
And if I later trigger the ng-change by changing the regular input field ("other"), it picks up the changes, or actually, it sets the submit button state correctly.
Now, I suspect this has to do with me calling the angular.element(this).scope().setFile(this) from my form instead of direct, $scope-bound method. But I cannot call $scope-bound method because it does not trigger - if I understood correctly, that's due to Angular not (yet) working with input type=file fields.
What can I do here?
I simply want to detect if there is a file or not so I can enable/disable the submit button appropriately.
I used followed flow that works for me:
<input type="file"
ng-model="upFile"
onchange="angular.element(this).scope().setFileEventListener(this)"
/>
From controller:
$scope.setFileEventListener = function(element) {
$scope.uploadedFile = element.files[0];
if ($scope.uploadedFile) {
$scope.$apply(function() {
$scope.upload_button_state = true;
});
}
}
Hope it will help.

Display error message for a textbox on blur

I'm brand new to javascript and knockout. I'm working on client side validation using knockout-validation and am having some trouble. I want textboxes that require some user input to show their error messages on blur (even if the user didn't enter anything). A problem I ran into is that I don't want the error messages to show up right away. I was able to get this working but was wondering if someone had a more elegant way to do this. The pseudo code for what I do is set a textbox's value as an observable and then subscribe that to hasfocus of the textbox. Here is the sample code of the view model and the fiddle to go with it:
self.firstName = ko.observable().extend({
required: true,
notify: 'always'
});
self.firstName.focused = ko.observable();
self.firstName.focused.subscribe(function(newVal) {
if(not the first time in the function and the value hasn't changed)
{
update the value to itself;
//if this is empty then it will trigger the "required" error message
}
});
http://jsfiddle.net/sderico/qAnxw/
I want to know if there's a nicer way to implement this functionality (or any other ways that aren't too convoluted). Thanks in advance!
You just need to specify the valueUpdate option to 'blur' on in your value binding. Then knockout will also update the value of firstName on the blur event which triggers the validation:
<input type="text" runat="server" ID="FirstName"
data-bind="value: firstName, valueUpdate: 'blur'"/>
Demo JSFiddle.

Categories