Function is called twice upon ng-blur and form submission - angularjs

In this plunk I have a form with an input field and a button. The input field's ng-blur and the form submission call the same function validate(). Problem is that when the cursor is on the input field AND the user clicks on "Submit" the function is triggered twice, as there's a blur when the focus passes to the button. In that scenario, I need the function to be called once.
If you look at the console.log you'll see how the function is triggered twice. Any ideas how to fix this?
HTML
<form name="myForm" ng-submit="validate(2)">
<input type="text" ng-model="someField" ng-blur="validate(1)" />
<br/>
<button type="submit">Submit</button>
</form>
Javascript
var app = angular.module('example', []);
app.controller('ctl', function ($scope) {
$scope.validate = function(x) {
console.log("field validated - " + x);
};
});

Here's I would like to solve this. I put a submitted variable which determines whether form is submitted or not. So, in the validate function, we first determine whether submitted is true or not. But, the important point here is we would need to make it false when we complete the API call. Something like this:
app.controller('ctl', function ($scope) {
$scope.submitted = false;
$scope.validate = function(x) {
if($scope.submitted === true) {
return;
}
$scope.submitted = true;
console.log("field validated - " + x);
// change $scope.submitted back to false in success and error callback of
// the API call
};
});
This solution has another side benefit. It stops users from submitting the form twice (using multiple clicks on submit button?)
We could have utilized formName.$submitted too but here one of the function calls will have it and another wouldn't.
Here's updated plunker

that's obvious, but you can always discriminate on the basis of event type, just pass an extra attribute denoting the type of event
EDIT : ng-change instead of ng-blur (description in comment)
html
<form name="myForm" ng-submit="validate(2)">
<input type="text" ng-model="someField" ng-change="validate(1)" />
<br/>
<button type="submit">Submit</button>
</form>
js
$scope.validate = function(x) {
console.log("field validated - " + x);
};

Related

angularjs -how to clear the form data from a clear button outside sitting outside the form

I have a form with a submit button which works fine. However I need to clear the form data from a clear button which is sitting outside the form on top right of the page. The clear button exist in a parent controller and sits above the form in top right header. The form sent from the clear button always comes up as undefined, which is because the clear button is not part of the form.
How do I pass the same instance of the form to clear? How do I clear the data? If this is a design issue I still need a workaround.
Here is a fiddle I have created to mimic that. Any help will be appreciated.
https://jsfiddle.net/SobDan/vj67rtb2/
<div ng-app>
<div class="col-md-10">
<h2>Todo</h2></div>
<div class="col-md-2">
<button class="btn pull-right" ng-click="clear(TodoForm)"> Close</button>
</div>
<br>
<div ng-controller="TodoCtrl">
<form name="TodoForm" ng-submit="addTodo()" name="testForm">
<input type="text" ng-model="todoText" size="30" placeholder="add new todo here">
<input class="btn-primary" type="submit" value="add">
</form>
</div>
function MainCtrl($scope) {
$scope.clear = function(form) {
alert(form); // the form is undefined
if (form.$dirty)
form.setPristine(); // clean this form
else
alert("form not dirty");
};
};
function TodoCtrl($scope) {
$scope.todoText = "test";
$scope.addTodo = function() {
alert("Submitted");
$scope.todoText = "";
// submit logic works fine
};
}
You should use $broadcast to communicate between controllers rather than trying to access the form which is outside scope.
Here is the fiddle and the explanation below
$broadcast function is used to broadcast events to all child $scope. Any interested child $scope can register to listen for the event using $on function. This functionality is used to communicate between controllers.
In your case, we signal to clear the form by broadcasting an event called clearForm from $rootScope. The TodoCtrl $scope listening on the event clearForm will receive a signal to clear form fields.
app.controller("MainCtrl", function($scope, $rootScope) {
$scope.clear = function(form) {
$rootScope.$broadcast("clearForm");
};
});
app.controller("TodoCtrl", function($scope) {
$scope.$on("clearForm", function() {
if ($scope.testForm.$dirty) {
$scope.testForm.$setPristine();
$scope.todoText = "";
} else {
alert("form not dirty");
}
});
});
AngularJS 1.1.x +
$scope.form.$setPristine() is only available from AngularJS version 1.1.x.
$setPristine() will only set the form status to pristine and will not clear the form field. You need to manually clear it by nullifying the $scope variables which will be reflected on the screen.
if ($scope.testForm.$dirty) {
$scope.testForm.$setPristine();
$scope.todoText = "";
}
AngularJS 1.0.x +
$setPristine function is not available in 1.0.x version.
The example Fiddle in your question seem to be configured to 1.0.x
In 1.0.x you simply clear the $scope variables
$scope.$on("clearForm", function() {
$scope.todoText = "";
});

Disable Text Entry - angular with jquery

Found these perfect solution here: Example
// Catch all events related to changes
$('#textbox').on('change keyup', function () {
// Remove invalid characters
var sanitized = $(this).val().replace(/[^-.0-9]/g, '');
// Remove non-leading minus signs
sanitized = sanitized.replace(/(.)-+/g, '$1');
// Remove the first point if there is more than one
sanitized = sanitized.replace(/\.(?=.*\.)/g, '');
// Update value
$(this).val(sanitized);
});
HTML
<input type="text" id="textbox" />
but i can't make this work inside a controller.
How can it be done?
One thing that you can do is use ng-change in your input like this:
<div ng-controller="yourCtrl as ctrl">
<input type="text" id="textbox" ng-change="ctrl.doSomething()" ng-model="ctrl.someVar">
</div>
in this way you will have access to the value of the input inside your controller that can be like this:
angular.module('yourModule').controller('yourCtrl',[function(){
var self = this;
self.doSomething = function(){
//you can access the value of the input using
// self.someVar
}
});
The ng-change (official doc: https://docs.angularjs.org/api/ng/input/input%5Btext%5D ) is "executed when input changes due to user interaction with the input element".
You can also use the ngKeyup (official doc: https://docs.angularjs.org/api/ng/directive/ngKeyup ) that is "evaluated upon keyup" how you connect it to a function is the same as ng-change. You will have only to choose one of the two.

Detect if the user fills the form with the autofill option of google chrome

If anyone can guide me on how to detect if the user fills the form with the autofill option of google chrome.
I have a directive where each time the user fills in the field and change the blur sends an event to google analytics.
But also I need to detect whether the user has filled the form with the autofill option of chrome and push the data for each of the fields to google analytics.
Part of my directive:
element.bind('blur', function (e) {
if ((e.target.value !== 0) && typeof value !== 'undefined') {
if (_.has(ga_data, 'sendEvent')) {
analyticsService.sendEvent(ga_data.sendEvent);
}
if (_.has(ga_data, 'action') && ga_data.action === 'blur') {
analyticsService.sendEvent(ga_data);
}
}
});
You can use two way data binding here and watch the ng model on the form field for changes
<form method="post">
First name:<input type="text" name="fname" ng-model="user.fname"/><br />
Last name: <input type="text" name="lname" ng-model="user.lname"/><br />
E-mail: <input type="text" name="email" ng-model="user.email"/><br />
Phone: <input type="text" name="phone" ng-model="user.phone"/><br />
Address: <input type="text" name="address" ng-model="user.address"/><br />
</form>
Then inside your angular controller you can do something of this sort.
angular.module('app', []).controller('AppController', function($scope){
$scope.user = { };
$scope.$watch('user', function(nv, ov){
console.log(nv);
}, true);
});
There might be some cases that you need to handle though, to prevent sending multiple requests because $watch function will be triggered every time the value in the text field changes.
Here is a fiddle which triggers $watch when any value in a form field changes, be it via autofill or manual entry by user.
In this case, the way to detect when Chrome auto-fills a form instead of the user doing it is by detecting the absence of an event having occurred, such as the keyup event. Consider the block of HTML and Javascript below. I have the text input field marked with a data attribute that is initially set to false. If the user fills out anything in the form by typing, then the attribute is set to true. The moment when you record whether or not the user filled out the form is on form submit. Then you can check the fields of the form and see if the user entered the input his or herself.
<form onsubmit="dosomething()">
<label for="somefield"></label>
<input type="text" id="somefield" onkeyup="this.setAttribute('data-entered', (this.value != '').toString());" data-entered="false" />
<input type="submit" value="submit"/>
</form>
The reason why you need to use a keyboard event and send the information when the form is submitted is because you can only tell if auto-fill took place when any of the fields have values even when the user typed nothing in. This part is less about code, and is more about what needs to be done so to take a proper measurement.
Based on input-directive src, angular sets up a listener for cases of change, input, paste etc.
And whenever the browser autofills the input element, this listener is called and viewValue is commited through array of $parsers as of here ngModel directive src.
So eventually you can avoid additional scope.$watch and just rely on $parsers to send ga track event just on linking phase of each input element with directive. And btw don't forget to destroy you parser func right after first usage (i.e. browser autofill), so it will not spam further on viewValue change.
Here's an example:
angular
.module('app', [])
.directive('detectPrefill', function() {
return {
require: 'ngModel',
link: {
pre: function(scope, element, attrs, ctrl) {
function detectPrefill (viewValue) {
//send GA data
//...
// just checking that detectPrefill func is destroyed after first usage
viewValue && console.log(viewValue);
ctrl.$parsers.splice(
ctrl.$parsers.indexOf(detectPrefill),
1
);
return viewValue;
}
ctrl.$parsers.push(detectPrefill);
}
}
};
});
Hope this helps.

how to show validation messages when form is submitted in angular js

I have form in which there are couple of text fields and an email field.
I want to validate all fields for required / mandatory validation. I want to validate email field for email format validation. I want to carry out validation only when I click on two buttons and show the validation messages at top of the page. I know that I can check for email and required validations on field and using ng-show and a flag I can show messages. However I want to check each field's value in a directive and then set the flag to true which will make the message appear.
Here is my HTML. this gets loaded why state provider in main page which also defines following template and a controller for it. so its just a view partial:
<form name="myForm">
<div ng-show="validationFailed">
<!-- here i want to display validation messages using cca.validationMsgs object -->
</div>
<input type="text" name="test" ng-model="cca.field1" require />
....
<input type="email" mg-model="cca.field2" />
<input type="button" name="mybutton" />
</form>
Now the controller defined in another JS file:
'use strict';
(function(){
angular.module('store', []);
app.controller("StoreController",function(){
var cca = this;
cca.validationMsgs = {};
cca.validationFailed = false; //this flag should decide whether validation messages should be displayed on html page or not.when its true they are shown.
...//other mapped fields
});
And here is unfinished directive which I want to define and write my logic. Logic will be something like this :
1) iterate all elements which have require set on them and check their
$validators object / $error.required object is set
2) if yes set validationFailed flag to true,add the validation message to validationMsgs object and break the loop.
3) check if email type field has $error.email object set and if yes
similarly set validationFailed flag to true and add corresponding message to the object. Not sure if I really need a directive for this. I would like to apply the directive inside a element.
app.directive("requireOnSubmit",function(){
var directiveDefinitionObject = {
restrict:'E',
//.... need to fill in
//I can use link funcion but not sure how to map
// validationMsgs and validationFailed objects in here.
};
return directiveDefinitionObject;
});
Without any code or use case to go after I'll just show you a generic way of validating input. I'm going to use a signup functionality as an example.
Create a service/factory which will carry out the validation and return a promise, if the validation fails it will reject the promise. If not, it will resolve it.
Important note: A promise can only be resolved once (to either be fulfilled or rejected), meaning that the first declaration of a resolve or reject will always "win", which means you can't override any resolve or reject. So in this example if a field is empty and a user's email is undefined, the error message will be All fields must be filled in and not Invalid email format.
auth.factory('validation', ['$q', function($q) {
return {
validateSignup: function(newUser) {
var q = $q.defer();
for (var info in newUser) {
if (newUser[info] === '') {
q.reject('All fields must be filled in');
}
}
if (newUser.email === undefined) {
q.reject('Invalid email format');
}
else if (newUser.password.length < 8) {
q.reject('The password is too short');
}
else if (newUser.password != newUser.confirmedPass) {
q.reject('The passwords do not match');
}
q.resolve(true);
return q.promise;
}
}
}]);
And then inject this into your controller
auth.controller('AuthCtrl', ['$scope', '$location', 'validation', function($scope, $location, validation) {
$scope.status = {
message: ''
}
// Make sure nothing is undefined or validation will throw error
$scope.newUser = {
email: '',
password: '',
confirmedPass: ''
}
$scope.register = function() {
// Validation message will be set to status.message
// This will also clear the message for each request
$scope.status = {
message: ''
}
validation.validateSignup($scope.newUser)
.catch(function(err) {
// The validation didn't go through,
// display the error to the user
$scope.status.message = err;
})
.then(function(status) {
// If validation goes through
if (status === true) {
// Do something
}
});
}
And in the HTML you can have something like this:
<form>
<div>
<label for="email">Email:</label>
<input type="email" id="email" ng-model="newUser.email">
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="confirm-pass" ng-model="newUser.password">
</div>
<div>
<label for="confirm-pass">Confirm password:</label>
<input type="password" id="confirm-pass" ng-model="newUser.confirmedPass">
</div>
<div>
<div>
<span ng-bind="status.message"></span>
</div>
</div>
<div>
<button ng-click="register(newUser)">Register</button>
</div>
</form>
You can use this example and modify it for your use case.

Validate Form with AngularJS on Enter keypress

I have a description of field in my AngularJS code:
input.form-control(type='password' placeholder='password' id='passwordInput'
name='passwordInput' ng-model='credentials.password
ng-enter='loginByEnter(credentials,loginForm.$valid)'
ng-maxlength='{{passwordMaxLen}}' required form-control)
The line in question is ng-enter one. The function loginByEnter is:
$scope.loginByEnter = function(credentials,htmlValidated) {
if (htmlValidated) {
$scope.login(credentials);
}
};
And custom directive ng-enter is
.directive('ngEnter', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
scope.$apply(function (){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
The purpose of all this: I want user to proceed with login when he netered password and hit Enter (so he does not have to press separate Login button elsewhere). But I also need to validate the form. Can it be done more elegantly, without querying a validation function on hitting enter?
You need to take a look at this article.
It explains how you can do form validation in AngularJs.
Simply put, you need to enclose the input tag inside a form tag and then you need to use the ng-submit directive on the form tag to allow the user to submit the form without hitting the login button.
You don't need to make use of your custom ng-enter directive.
"ngEnter" or similar keyboard event processors are not needed for form submission. One can use ngSubmit on <form> elements to handle either key press (Enter) or button click submission.
View
<form ng-submit="onLoginSubmit()">
<input type="text" ng-model="username" />
<input type="password" ng-model="password" />
<input type="submit" />
</form>
Controller
function Controller($scope, ...) {
$scope.onLoginSubmit = function() {
if(validate($scope.username, $scope.password)) {
// Magic
}
}
}

Resources