AngularJS form validation: indicate required fields to user - angularjs

I would like my form labels to display a red asterisk next to the label when the corresponding form control has a required attribute.
Instead of hard coding the asterisk, I desire a way to append the asterisk to the label dynamically during page load if the label's corresponding input, select or textarea is required (the element the label corresponds to).
I created the directive below, and the directive works. But is there a better, more native way to accomplish my goal? This directive finds all the div.form-group containers and adds a red * character after the label if the corresponding form control inside the div.form-group has a required attribute.
myApp.directive('labelsRequired',function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs){
elem.find('div.form-group').each(function(i, formGroup){
var formControls = $(formGroup).find('input, select, textarea');
console.log(formControls)
if (0 !== formControls.length && undefined !== $(formControls[0]).attr('required')){
jLabel = $(formGroup).find('label');
jLabel.html(jLabel.html()+ "<span class='red-color'>*</span>");
}
})
}
}
});
The directive assumes all inputs, selects, and textareas are inside a div.form-group container.
<div class='form-group'>
<label>First Name</label><!-- this label gets an asterisk -->
<input name='fname' required />
</div>
<div class='form-group'>
<label>Favorite Food</label><!-- this label does not get an asterisk -->
<input name='favFood' />
</div>

You don't need a directive, there are built-in form properties you can use with filters like ng-show, look:
<div ng-app="myApp" ng-controller="myCtrl">
<form name="userForm" novalidate>
<div class='form-group'>
<label>First Name</label>
<input name='fname' ng-model="fname" required />
<label ng-show="userForm.fname.$dirty && userForm.fname.$error.required">* Required field</label>
</div>
<button type="submit">Submit</button>
</form>
</div>
If you define an ng-model for the input you can deal with it looking if it is filled or not. You can also check it only after the user "dirty" it with userForm.fname.$dirty, so the label will be shown only after a user try to input something but then clear it. Try playing with it here JSFiddle

Building off of Corey's answer:
I just used compile rather than link, as I saw that my required attribute was not being applied to my input elements. I also included a select tag for any dropdowns that I had.
app.directive('inputRequired', function () {
return {
restrict: 'A',
compile: function (elem) {
elem.find('label').append("<sup><i class='fa fa-asterisk'></i></sup>");
elem.find('input').attr('required', 'required');
elem.find('select').attr('required', 'required');
}
};
});

If you're not using the built-in Angular validation, you could restructure your directive and attach it to your .form-group element. Like this:
app.directive('inputRequired', function() {
return {
restrict: 'A',
link: function(scope, elem, attr) {
elem.find('label').append('<span>*</span>');
elem.find('input').attr('required', 'required');
}
};
});
Your HTML would then look like:
<div class="form-group" input-required>
<label>Name</label>
<input name="name" />
</div>
<div class="form-group">
<label>Food</label>
<input name="food" />
</div>
However, if you haven't looked into the built-in validation with Angular, I would recommend using it.

This might come too late and it might not be too elegant but it works, if anyone needs it:
<label ng-show="userForm.fname.$validators.hasOwnProperty('required')">* Required field</label>

Related

Exclude control from affecting form $pristine

I have a text input within a directive that I want to use as a filter for displaying a list of items, but I don't want the entering of a filter to affect the containing forms $pristine value so that entering a filter doesn't enable the save and show the Reset. How do I do this in angularJS (1.6.x)?
directive template
<form name='myForm'>
<input placeholder="Filter" class='form-control' type='text' ng-model='vm.searchText'>
<ul><li ng-repeat='item in vm.list | filter:vm.searchText'/></ul>
<div>
<br>
<button class='btn btn-primary' ng-click='vm.save()' ng-disabled="myForm.$pristine || frmCrm.$invalid">Save</button>
<div class='pull-right'>
<button class='btn btn-warning' ng-click="vm.reset()" ng-hide="myForm.$pristine">Reset</button>
</div>
</div>
</form>
Yes I know I could easily put the filter input outside the form in this example, but in my actual situation that isn't feasible as I have nested forms and one that wraps basically the whole page.
here's plnkr example:
http://plnkr.co/edit/y1dJLPbyvlZuIW1f7ey9
You can override the $setDirty and $setPristine methods of the ngModel:
angular.module('xyz').directive('dontCheck', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
//set to empty functions
ngModelCtrl.$setPristine = angular.noop;
ngModelCtrl.$setDirty = angular.noop;
}
}
});
I've forked your plunker, you can try the solution:
http://plnkr.co/edit/6UVTQjJtwu4mOXVt7sPT?p=preview
Edit:
Updated plunker to follow your code style. I will leave the code here as is.

angular JS dynamic tooltip creation

EDIT:
It seems like the Error or wrong handling is because the scope does not gets updated when the email field is not valid... is there a way to chang that?
this is my first question on stackoverflow so i hope i will do it right.
I am pretty new to angular js and i am creating some basics at the moment.
in my demo app i created a normal form in a bootstrap style and i was planing to create a directive to show the errors in a bootstrap way. so far so good. that was working and my next step was to create a angular js bootstrap tooltip directive when the form is not valid. the thing is, that i wanna do this dynamic.
i post some code to explain it better:
<b>HTML:</b>
<div class="container" ng-controller="LoginCtrl as vm">
<form id="login" name="vm.loginForm" class="form-signin" ng-submit="vm.login()" novalidate>
<div show-errors>
<input type="email" name="username" ng-model="vm.credentials.username" class="form-control" placeholder="Email address" required autofocus>
</div>
<div show-errors>
<input type="password" name="password" ng-model="vm.credentials.password" class="form-control" placeholder="Password" required>
</div>
<button class="btn btn-lg btn-primary btn-block" id="submit" type="submit">
Login
<span class="spinner"><i class="fa fa-refresh fa-spin"></i></span>
</button>
</form>
</div>
<b>showError Directive:</b>
(function () {
angular.module('testapp.Validate', []).directive('showErrors', validationDirective);
/**
* #name validate directive
*/
function validationDirective($compile) {
return {
require: '^form',
restrict: 'A',
link: function (scope, el, attrs, formCtrl) {
var inputNgEl = angular.element(el[0].querySelector("[name]"));
var inputName = inputNgEl.attr('name');
inputNgEl.bind('blur', function () {
toogle(inputNgEl, el, formCtrl[inputName]);
});
scope.$on('show-errors-check', function () {
toogle(inputNgEl, el, formCtrl[inputName]);
});
}
}
}
function toogle(inputNgEl, fromGroup, inputField) {
fromGroup.toggleClass('has-feedback has-error', inputField.$invalid);
if (inputField.$invalid && !fromGroup[0].querySelector(".glyphicon-remove")) {
inputNgEl.after('<span class="glyphicon form-control-feedback glyphicon-remove"></span>');
} else if (!inputField.$invalid) {
if (fromGroup[0].querySelector(".glyphicon-remove") != null) {
fromGroup[0].querySelector(".glyphicon-remove").remove();
}
}
}
})();
That is working so far . it just adds a has-feedback,has-error class to the parent div and a span with an error-icon after the input.
but back to my plan, now I also want to create a dynamic tooltip for the input. so I planed to add something like that in the "toogle" function
inputNgEl.attr('uib-tooltip',"aaaa");
inputNgEl.attr('tooltip-placement',"right");
inputNgEl.attr('tooltip-trigger',"mouseenter");
inputNgEl.attr('tooltip-class',"testTool");
But that is not working because the input field got already compiled before.
so I asked google about it and there I found some solutions with $compile
$compile(inputNgEl)(scope);
But when I am using it, and I type in a valid email address the field gets reset. aaa#aaa (still working) but after I add the aaa#aaa. (the field gets reseted - I guess compiled again). the tooltip would work btw.
Can anybody help me with that or is there a better solution to create a dynamic angular bootstrap tooltip?
Maybe you need to add a $watch on the input element, and see if it changes, add a tooltip in the input element and compile it

How to show a validationmessage on the validation of two fields and make the form invalid

I want to make a validation on the sum of two fields, both are numeric input, but the total must not be larger than 100.
So I thought I'd make a hidden input which reads the total from my controller, and create a custom validationdirective to check the value to be <= 100 and create a span which checks whether that input is valid or not and then show/hide it.
But the input is bound to a function (which adds the two fields together) on my controller, so I can not use ng-model.
So I thought I use ng-bind, but then my validation directive complained about this:
require: 'ngModel',, because I don't have a ng-model anymore.
So I deleted that require, but then the ctrl.$validators was not present anymore and that gave me an error....
And now I am lost :-)
My validationdirective is this:
angular.module(Config.name).directive('maxvalue', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$validators.maxvalue = function (modelValue, viewValue) {
//validation here
};
}
};
});
Can you give my a way to solve this?
EDIT
maybe my question is broader: I have more of these like alidations, where the validation is not tied to a single input field. I also have two buttons which a user can click (sort of a radio button, but then implemented with 2 buttons). I also want to show a span when no button is clicked + make the form invalid.
But I do not know how to say to the form 'you are invalid' neither from the form itself neither from the controller.
For set form or input valid\invalid you can use $setValidity.
But i recommend use special library use-form-error.
Example of using you can see on jsfiddle
angular.module('ExampleApp', ['use', 'ngMessages'])
.controller('ExampleController', function($scope) {
});
.errors {
color: maroon
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular-messages.min.js"></script>
<script src="https://cdn.rawgit.com/Stepan-Kasyanenko/use-form-error/master/src/use-form-error.js"></script>
<div ng-app="ExampleApp">
<div ng-controller="ExampleController">
<form name="myForm">
<label>Enter your digit 1:</label>
<input type="text" ng-model="digit1" name="myDigit1" />
<br>
<label>Enter your digit 2:</label>
<input type="text" ng-model="digit2" name="myDigit3" />
<div use-form-error="isGeaterHundred" use-error-expression="digit1*1+digit2*1>100" ng-messages="myForm.$error" class="errors">
<div ng-message="isGeaterHundred">Your sum is greated then 100</div>
</div>
<input type="submit" ng-disabled="myForm.$invalid">
</form>
</div>
</div>

Angular directive attributes aren't being passed through

I'm trying to shorten my form code by making directives for each element, however my directive is displaying none of what I'm passing to it and the model isn't being bound.
HTML:
<formstring dataBinding="project.title" dataTitle="Title" dataPlaceholder="title" />
directive:
app.directive('formstring', function () {
return {
restrict: 'AEC',
dataBinding: '=',
dataTitle: '#dataTitle',
dataPlaceholder: '#dataPlaceholder',
dataHelp: '#dataHelp',
templateUrl: '/app/js/directives/form/string.html',
};
});
string.html:
<div class="form-group"> 2 <label for="{{dataTitle}}" class="col-sm-2 control-label">{{dataTitle}}</label >
<div class="col-sm-10">
<input type="text" class="form-control" id="{{dataTitle}}" placeholder="{{da taPlaceholder}}" ng-model="dataBinding">
<p ng-show="dataHelp" class="help-block">{{dataHelp}}</p>
</div>
</div>
project is a $scope object that has an attribute 'title'.
What am I missing? Why does this show up at a blank input with none of the attributes filled in and why does the binding not work?
You haven't understood how directives are configured correctly. I suggest you read the documentation, it may help you understand better.
In the meantime, here is what your HTML, directive code and template should look like (there is also a working demonstration on Plunkr):
HTML:
<formstring data-binding="project.title"
data-title="Title Demo"
data-placeholder="title placeholder"
data-help="My help text">
</formstring>
directive:
app.directive('formstring', function () {
return {
restrict: 'E',
scope: {
binding: '=',
title: '#',
placeholder: '#',
help: '#'
},
templateUrl: '/app/js/directives/form/string.html',
};
});
Template (string.html):
<div class="form-group">
<label for="{{title}}" class="col-sm-2 control-label">
{{title}}
</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="{{title}}" placeholder="{{placeholder}}" ng-model="binding">
<p ng-show="help" class="help-block">{{help}}</p>
</div>
</div>
You need to change how you're creating isolate scope:
app.directive('formstring', function () {
return {
restrict: 'AEC',
scope: {
dataBinding: '=',
dataTitle: '#dataTitle',
dataPlaceholder: '#dataPlaceholder',
dataHelp: '#dataHelp'
},
templateUrl: '/app/js/directives/form/string.html',
};
});
Read the doc for details about what an isolate/isolated scope means because it has an effect on the overall scope.
edit:
I didn't notice this additional issue before. Your camel case scope properties become snake cased when you use your directive (see Mobin's answer):
<formstring data-binding="project.title" data-title="Title" data-placeholder="title" />
In your template, however, the properties are still camel cased as you have:
<div class="form-group"> 2 <label for="{{dataTitle}}" class="col-sm-2 control-label">{{dataTitle}}</label >
<div class="col-sm-10">
<input type="text" class="form-control" id="{{dataTitle}}" placeholder="{{da taPlaceholder}}" ng-model="dataBinding">
<p ng-show="dataHelp" class="help-block">{{dataHelp}}</p>
</div>
</div>
This is because the bindings in your template are JSON properties, whereas the attributes when you use your directive's properties are XML properties.
There are minor tweaks I'd apply to your template, for instance id="{{dataTitle}}" can easily break HTML standards that require the id attribute is unique... you probably want to use name="{{dataTitle}}" instead. name could still cause issues, but it wont' break document.getElementById for example.
Also, I'd use ng-bind whenever possible:
<p ng-show="dataHelp" class="help-block" ng-bind="dataHelp"></p>
It should be
<formstring data-binding="project.title" data-title="Title" data-placeholder="title" />
.

Form Validation error while using angularjs

I'm trying to add a simple validation for my input text box using angularjs and trying to show an inline error message. I have tried a lot, but still I'm not able to get the $valid, $ error, $invalid etc. These attributes are getting as undefined. I have given the reference to angularjs.js file. All other functionalities are working, but don't know what's happening. I'm new to angularjs, hope someone will help me in this regard. My code snippet is given below.
<sp-modal-dialog show='modalShown'>
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
EDIT:
I'm using a directive to show the popup form. Please see the code below.
//Directive to link the modalDialog as Element
contextualHelpModule.directive('spModalDialog', function () {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true,
transclude: true,
link: function (scope, element, attrs) {
scope.hideModal = function () {
scope.show = false;
};
},
template: '<form id="popupForm" name="popupForm" novalidate><div ng-show="show"><div class="popup-main" ng-show="show"><div class="popup-container"><div class="popup-content" ng-transclude><div ng-click="hideModal()" class="popup-close">X</div></div></div></div><div class="popup-overlay"></div></div></form>'
};
you need to wrap that into a form:
<form name="popupForm">
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
</form>
I hope that it helps.
Yes, as jvrdelafuente explains, you will need to wrap it in a
<form name="popupForm">
tag. Once you do that, then you will have access to the properties via popupForm.txtTitle.$error.required, etc. Otherwise, they are not defined as you have observed. Unfortunately, I have recently learned that if you are placing your form tag in an aspx page using a SharePoint master page, the master page places its own form tag without a name attribute. ASP.NET will then strip your form out because it does not allow a form within a form. I am still searching for the rest of the story.
Update....
To be able to get past the issue with ASP.NET stripping out the AngularJS form tag, use an ng-form tag instead:
<ng-form name="popupForm">
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
</form>
Use ng-form at place of form, it will work fine, as see code in below
<ng-form name="popupForm">
<label for="title">Title <span>*</span></label>
<input type="text" name="txtTitle" ng-model="Model.Title" ng-maxlength="20" required>
<div ng-show="popupForm.txtTitle.$invalid">Invalid:
<span ng-show="popupForm.txtTitle.$error.required">Title is mandatory.</span>
<span ng-show="popupForm.txtTitle.$error.maxlength">Title should contain atleast 20 characters.</span>
</div>
</ng-form>

Resources