How can I cancel a form submit from an angularjs directive - angularjs

I am creating a validation directive that shows a message when form fields are invalid in a form. I would like to show the message and cancel the submit if the fields are not valid.
I have succeeded in showing the validation messages on submit by requiring the ngModel and form controllers but then I can't seem to find a way to use the FormController to cancel the form submit.
I have prepared a plunker here with my issue.
As you can see, it shows the error but I can't prevent the submit function from firing.
// Code goes here
var directiveName = "fcValidate";
angular.module("app", [])
.directive(directiveName, ["$timeout", validatorDirective])
.controller("PageCtrl", [pageCtrl]);
function validatorDirective($timeout) {
return {
restrict: "A",
require: ["^ngModel", "?^form"],
link: link
};
function link(scope, element, attributes, controllers) {
var modelCtrl = controllers[0];
var formCtrl = controllers[1];
// Validation.
$timeout(run);
function run() {
var requiredMessage = "Please enter the %(field)s.",
minLengthMessage = "Sorry, but the %(field)s cannot be shorter than %(minLength)s characters.",
maxLengthMessage = "Sorry, but the %(field)s cannot be longer than %(maxLength)s characters.",
minValueMessage = "Sorry, but the %(field)s cannot be less than %(min)s.",
maxValueMessage = "Sorry, but the %(field)s cannot be greater than %(max)s.",
invalidNumberMessage = "Please ensure that the %(field)s is a valid number.";
var content = null;
var field = attributes.name;
if (!field) {
return;
}
var toWatch = function () {
if (formCtrl) {
return formCtrl.$submitted;
}
return modelCtrl.$error;
};
scope.$watchCollection(toWatch, function (newValues, oldValues) {
var error = modelCtrl["$error"];
var invalid = modelCtrl["$invalid"];
var dirty = modelCtrl["$dirty"];
if ((formCtrl && !formCtrl.$submitted) || (!formCtrl && (_.keys(newValues) === _.keys(oldValues))) || !invalid || !dirty) {
return;
}
var msgTpl = null;
var fieldName = attributes[directiveName];
if (fieldName) {
fieldName = fieldName.toLowerCase();
}
if (error.required) {
msgTpl = requiredMessage;
} else if (error.minlength) {
msgTpl = minLengthMessage;
} else if (error.maxlength){
msgTpl = maxLengthMessage;
} else if (error.min) {
msgTpl = minValueMessage;
} else if (error.max){
msgTpl = maxValueMessage;
} else if (error.number) {
msgTpl = invalidNumberMessage;
}
if (fieldName) {
var data = {
field: fieldName || "",
min: attributes.min,
max: attributes.max,
minLength: attributes.minlength,
maxLength: attributes.maxlength
};
if (msgTpl) {
content = _.string.sprintf(msgTpl, data);
} else {
content = fieldName;
}
}
// Show message...
alert(content);
// Cancel the form submit here...
});
}
}
}
function pageCtrl() {
var vm = this;
vm.user = {};
vm.submit = submit;
function submit() {
console.log(vm.user);
}
}
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.string/2.3.0/underscore.string.min.js"></script>
<script src="https://code.angularjs.org/1.3.5/angular.js"></script>
<body data-ng-app="app" data-ng-controller="PageCtrl as vm">
<form data-ng-submit="vm.submit()">
<input type="text" name="firstName" required="" minlength="2" placeholder="First Name" data-ng-model="vm.user.firstName" data-fc-validate="First Name" />
<button type="submit">Submit</button>
</form>
</body>
My question is, how can I cancel the form submit? Any help will be appreciated.

if it is like in jQuery, if you bind the function to a "submit" event you only need to return false in the callback.

Is disabling submit button enough for you? If yes, define name of form and then use it in ng-disabled on submit button
<form data-ng-submit="vm.submit()" name="validatingForm">
<input type="text" name="firstName" required="" minlength="2" placeholder="First Name" data-ng-model="vm.user.firstName" data-fc-validate="First Name" />
<button type="submit" ng-disabled="validatingForm.$invalid">Submit</button>
</form>
Also I think ng-messages can be usefull for your validation messages - https://docs.angularjs.org/api/ngMessages/directive/ngMessages

Related

Field update after autocompletion with angularJS

I'm quite new to AngularJS and struggling a bit to have some input fields updated after an autocompletion event using google maps.
The idea is that when the user inputs his city/zip code, I would update 3 fields which are themselves linked to an object.
So far, I managed to have a working code except that sometimes the fields are not updated immediately : I have to autocomplete twice so that the good value will appear in the fields.
I've tweaked an existing angular directive in order to get what I want but since this is new to me, I dont know if I'm using the correct approach.
Below is the JS directive I use :
angular.module( "ngVilleAutocomplete", [])
.directive('ngAutocomplete', function($parse) {
return {
scope: {
details: '=',
ngAutocomplete: '=',
options: '=',
data: '='
},
link: function(scope, element, attrs, model) {
//options for autocomplete
var opts
//convert options provided to opts
var initOpts = function() {
opts = {}
if (scope.options) {
if (scope.options.types) {
opts.types = []
opts.types.push(scope.options.types)
}
if (scope.options.bounds) {
opts.bounds = scope.options.bounds
}
if (scope.options.country) {
opts.componentRestrictions = {
country: scope.options.country
}
}
}
}
initOpts()
//create new autocomplete
//reinitializes on every change of the options provided
var newAutocomplete = function() {
scope.gPlace = new google.maps.places.Autocomplete(element[0], opts);
google.maps.event.addListener(scope.gPlace, 'place_changed', function() {
scope.$apply(function() {
scope.details = scope.gPlace.getPlace();
//console.log(scope.details)
var HasCP = false;
for (var i=0 ; i<scope.details.address_components.length ; i++){
for (var j=0 ; j<scope.details.address_components[i].types.length ; j++){
if (scope.details.address_components[i].types[j] == 'postal_code' && scope.data.CP != 'undefined'){
scope.data.CP = scope.details.address_components[i].long_name;
HasCP = true;
} else if (scope.details.address_components[i].types[j] == 'locality' && scope.data.Ville != 'undefined') {
scope.data.Ville = scope.details.address_components[i].long_name;
} else if (scope.details.address_components[i].types[j] == 'country' && scope.data.Pays != 'undefined') {
scope.data.Pays = scope.details.address_components[i].long_name;
}
}
}
if (!HasCP){
var latlng = {lat: scope.details.geometry.location.lat(), lng: scope.details.geometry.location.lng()};
var geocoder = new google.maps.Geocoder;
geocoder.geocode({'location': latlng}, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
for (var i=0 ; i<results[0].address_components.length ; i++){
for (var j=0 ; j<results[0].address_components[i].types.length ; j++){
if (results[0].address_components[i].types[j] == 'postal_code' && scope.data.CP != 'undefined'){
scope.data.CP = results[0].address_components[i].long_name;
console.log('pc trouvé :' + scope.data.CP);
}
}
}
}
});
}
//console.log(scope.data)
scope.ngAutocomplete = element.val();
});
})
}
newAutocomplete()
//watch options provided to directive
scope.watchOptions = function () {
return scope.options
};
scope.$watch(scope.watchOptions, function () {
initOpts()
newAutocomplete()
element[0].value = '';
scope.ngAutocomplete = element.val();
}, true);
}
};
});
The matching HTML code is below :
<div class="form-group">
<lable>Code postal : </label>
<input type="text" id="Autocomplete" class="form-control" ng-autocomplete="cities_autocomplete" details="cities_autocomplete_details" options="cities_autocomplete_options" data="client" placeholder="Code postal" ng-model="client.CP" />
</div>
<div class="form-group">
<lable>Ville : </label>
<input type="text" id="Autocomplete" class="form-control" ng-autocomplete="cities_autocomplete" details="cities_autocomplete_details" options="cities_autocomplete_options" data="client" placeholder="Ville" ng-model="client.Ville" />
</div>
<div class="form-group">
<lable>Pays : </label>
<input type="text" class="form-control" name="Pays" ng-model="client.Pays" placeholder="Pays" />
</div>
You'll see that I pass the "client" object directly to my directive which then updates this object. I expected angular to update the html page as soon as the values of the client object are updated but I will not always be the case :
If I search twice the same city, the values are not updated
If I search a city, Google wont send me a zip code so I have to do another request to the geocoding service and I get the zipcode in return but while my client.CP field is correctly updated, changes are not visible in the CP input field until I do another search.
Thanks in advance for any advice on what I'm doing wrong.

remove $error message from several input fields in the form using directive

I have several fields in the form:
<input name="participant{{$index}}email" type="email" ng-model="participant.email" ng-trim="true"
required ng-minlength="1" ng-maxlength="255"
email-uniqueness-validator="{{$index}}">
I use the emailUniquenessValidator directive to check if any participant entered the same email. If so I display error message:
<div ng-messages="enroll['participant' + $index + 'email'].$error">
<div ng-message="emailUniqueness">The email addresses must be different for every applicant...</div>
</div>
The problem is when I have two fields with the same email and both of them show error. Then user edits one email so it's different than any other email and the error message on the field disappears as expected, but how can I remove the error message from the second email field that became unique by editing the first email field?
The directive:
.directive('emailUniquenessValidator',
function() {
return {
require : 'ngModel',
link: function (scope, element, attrs, ngModel) {
scope.$watch(attrs.ngModel, function () {
var currentEmailFieldNo = attrs.emailUniquenessValidator;
var diffEmails = differentEmails(scope, currentEmailFieldNo);
ngModel.$setValidity('emailUniqueness', diffEmails);
if (!diffEmails) {//one field has changed and there is no duplicates, but we need to remove validation errors from the other field
cleanDuplicateEmailErrors(scope);
}
});
}
}
});
differentEmails function:
function differentEmails(scope, currentEmailFieldNo) {
differentEmails = true;
var currentEmail = currentEmailFieldNo >= 0
? scope.applicantEnrollDto.participants[currentEmailFieldNo].email
: scope.applicantEnrollDto.email;
var mainEmail = scope.applicantEnrollDto.email;
if (currentEmailFieldNo < 0) {
if (emailInArray(currentEmail, scope.applicantEnrollDto.participants)) {
differentEmails = false;
}
} else {
var applicantsNo = scope.applicantEnrollDto.participants.length
var differentEmails = true;
if (applicantsNo) {
differentEmails = !hasDuplicates(scope.applicantEnrollDto.participants);
if (differentEmails) {
if (currentEmail === mainEmail) {
differentEmails = false;
}
}
}
}
return differentEmails;
}
The problem was solved easily by accessing form in the scope
$scope.form["participant"+i+"email"].$setValidity('emailUniqueness', errorsOff);

AngularJS clear validation $error after input's changing

Updated question with fiddle.
Original is here: https://stackoverflow.com/questions/31874313/angularjs-clean-remote-validation-error-after-change-input
In my form I have two validations. First is local, second is remote.
So this is my example
<form ng-controller="MyCtrl" name="Form">
<div class="form-group">
<label class="control-label">
First Name
</label>
<input type="text" class="form-control" name="firstName" ng-model="myModel.firstName" required />
<span class="error" ng-if="Form.firstName.$dirty && Form.firstName.$invalid" ng-repeat="(e, b) in Form.firstName.$error">{{e}}</span>
</div>
<input type="submit" ng-click="submit(Form)">
</form>
Here is Controller
function MyCtrl($scope, $element) {
$scope.submit = function (form) {
if (form.$invalid) {
renderErrors(form);
return;
}
console.log('local validation passed');
// imitation of remote error
// send, then data
if($scope.myModel.firstName === 'Tom')
renderServerErrors({firstName: ['Already in use']}, form);
else
alert('Success');
}
/**
* Errors will appear below each wrong input
*/
var renderErrors = function(form){
var field = null;
for (field in form) {
if (field[0] != '$') {
if (form[field].$pristine) {
form[field].$dirty = true;
}
}
}
};
/**
* Server errors will appear below each wrong input
*/
var renderServerErrors = function(err, form){
var field = null;
_.each(err, function(errors, key) {
_.each(errors, function(e) {
form[key].$dirty = true;
form[key].$setValidity(e, false);
});
});
}
}
http://jsfiddle.net/uwozaof9/6/
If you type 'Tom' into input - you will never submit form more..
And I want to delete server errors from input's error stack on it's change.
Please help!
It seems you only set invalid but don't set valid after it was corrected. IF you are doing yourself you also have to implement setting $valid if the imput is valid.

Time validation in angular js

I have a field which takes inputs of time something like 12:00 / 24:00
I want used to be able tot fill only digits and : in the field and on change validate it with proper time. I want to do it in angular way. How do i do it.
Here is the code i have written. This can be done in 1 directive also. This code works, But not better code.
<input type="text" ng-model-options="{allowInvalid: true}" ng-blur="validateHhMm(this)" ng-model='time' time-only/>
angular.module('abc').directive('timeOnly', function(){
return {
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
modelCtrl.$parsers.push(function (inputValue) {
var transformedInput = inputValue ? inputValue.replace(/[^\d:]/g,'') : null;
if (transformedInput!=inputValue) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
return transformedInput;
});
}
};
});
$scope.validateHhMm = function (inputField)
{
var errorMsg = "";
var regs = '';
// regular expression to match required time format
var re = /^(\d{1,2}):(\d{2})?$/;
var inputvalue = $scope.time;
if(inputvalue != '')
{
if(regs = inputvalue.match(re))
{
if(regs[4])
{
// 12-hour time format with am/pm
if(regs[1] < 1 || regs[1] > 12)
{
errorMsg = "Invalid value for hours: " + regs[1];
}
}
else
{
if(regs[1] > 23)
{
errorMsg = "Invalid value for hours: " + regs[1];
}
}
if(!errorMsg && regs[2] > 59)
{
errorMsg = "Invalid value for minutes: " + regs[2];
}
}
else
{
errorMsg = "Invalid time format: " + inputvalue;
}
}
if(errorMsg != "")
{
//console.log(errorMsg);
//field.focus();
$scope.time = '';
return false;
}
return true;
};
try this way
<input type="time" id="exampleInput" name="input" ng-model="example.value"
placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
<span class="error" ng-show="myForm.input.$error.time">
Not a valid date!</span>
</div>
Here is the plunker
This is the example from here

Need to require only one of a group of fields with Parsley

I am using Parsley.js for validating a form submission on a project. One of my needs is to have Parsley require that at least one of three fields have data in them, and only fail validation if none of the three fields has data.
I am not sure from the documentation, how to accomplish this. I already have Parsley validation working on the rest of the form.
You can do that with a custom validator like so
var CheckReccursion = 0;
window.Parsley.addValidator('min3', {
validateString: function (value, requirement, instance) {
var notice =$('#notice').html(' ');
var group = $(requirement);//a class
var FieldsEmpty = 0;
var FieldsNotEmpty = 0;
var count = 0
group.each(function () {
var _val = $(this).val()
var length = _val.length
if (length > 0) {
FieldsNotEmpty++;
}
else {
FieldsEmpty++;
}
count++;
})
var isValid = (FieldsNotEmpty >=1)
//recursively execute
group.each(function (index) {
if (CheckReccursion === index) {
CheckReccursion++;
$(this).parsley().validate();
CheckReccursion = 0;
}
})
return isValid;
}
});
$(function () {
var ok=false;
var notice =$('#notice');
$('#form1').parsley().on('form:validated', function(formInstance) {
ok = formInstance.isValid({force: true});
})
.on('form:submit', function() {
if(!ok){
notice.html('Please fill at least 1 field');
return false;
}
else{
notice.html('okay');
return false;//change to true to submit form here
}
});
});
then add parsley attributes to the group of fields like so:
<form id="form1" data-parsley-validate="true">
<input type="text" name="field1"
data-parsley-min3 = ".group1"
data-parsley-min3-message = "At least 1 must be filled"
class="group1">
<input type="text" name="field2"
data-parsley-min3 = ".group1"
data-parsley-min3-message = "At least 1 must be filled"
class="group1">
<input type="text" name="field3"
data-parsley-min3 = ".group1"
data-parsley-min3-message = "At least 1 must be filled"
class="group1">
<span id="notice"></span>
<input type="submit" value="Submit">
</form>
Check out this fiddle https://jsfiddle.net/xcoL5Lur/6/
My advice is to add hidden checkbox element with the attribute:
data-parsley-mincheck="1"
now just add javascript code that checks the hidden checkbox attribute when your form input has value (and the opposite).
notice that you will need to add extra attribute to your hidden checkbox:
data-parsley-error-message="Please fill at least one input"
Another approach is to using data-parsley-group and the isValid({group,force}) method.
<input type="text" name="input1" data-parsley-group="group1">
<input type="text" name="input2" data-parsley-group="group2">
<input type="text" name="input3" data-parsley-group="group3">
$('#myform').parsley().on('form:validate', function (formInstance) {
if(formInstance.isValid({group: 'group1', force: true}) ||
formInstance.isValid({group: 'group2', force: true}) ||
formInstance.isValid({group: 'group3', force: true})) {
//do nothing
}
else {
$('#errorContainer').html('You must correctly fill at least one of these three groups!');
formInstance.validationResult = false;
}
});
you can add as many as parsley's attributes as you wish, like data-parsley-type="email" that will be validated when the given input is not empty.
we set the force: true because it it forces validation even on non-required.fields.
the html render for the errorContainer is needed because the isValid method does not affect UI nor fires events.

Resources