AngularJS dynamic required attribute in directive and form validation - javascript

I have a directive that receives whether an element should be required or not from a REST api. Right now, I can't seem to get the form to invalidate when an attribute is set to required.
So, in essence I'm able to dynamically add the 'required' attribute from the directive below, but it doesn't invalidate the form. Looking through chrome I see that, even though the required attribute exists, a required entry in the $error array doesn't exist.
app.directive('requireiftrue', function ($compile) {
return {
require: '?ngModel',
link: function (scope, el, attrs, ngModel) {
if (!ngModel) {
return;
}
if(attrs.requireiftrue==="true"){
console.log('should require');
el.attr('required', true);
$compile(el.contents())(scope);
}
else{
console.log('should not require');
}
}
};
});
Here's a jsfiddle to illustrate the problem. And, here's sample JSON returned from my rest API
{
race: false,
martialStatus: true,
}
EDIT: While the accepted answer got me up and running, I still had a good bit of finagling to do.
Namely:
1. Resolving a deferred promise to ensure that my form actually receives the required fields to validate
2. observing my 'requireiftrue' attribute
My solution
module config:
function config($stateProvider) {
$stateProvider
.state('testState', {
url: '/test/form',
controller: 'TestCtrl',
templateUrl: 'test/form/testForm.tpl.html',
resolve: {
formDefaultService: function getFormDefaults($q, dataservice) {
// Set up a promise to return
var deferred = $q.defer();
var myData = dataservice.getFormDefaults();
deferred.resolve(myData);
return deferred.promise;
//return
}
},
data: {
pageTitle: 'Test Info'
}
});
}
And, finally the directive / HTML that receives api data:
Directive:
.directive('requireiftrue', function ($compile) {
return {
require: '?ngModel',
link: function (scope, el, attrs, ngModel) {
if (!ngModel) {
return;
}
attrs.$observe('requireiftrue', function(value){
if(value==="true"){
el.attr('required', true);
el.removeAttr('requireiftrue');
$compile(el[0])(scope);
}
});
}
};
});
HTML:
<input max="40"
requireiftrue={{defaults.validation.name}}
validNumber
id="name"
name="name"
type="text"
ng-model="test.name"
class="form-control">

You had two issues:
The first is el.contents() returned an empty array. so The first thing you should do is change it to el[0]. But had el.contents() worked you would hav had a much more serious problem. You would have been trying to compile a directive that has itself as a directive which would lead to an infinite loop (well until the browser crashed any way).
So here is the revised code:
var app = angular.module('form-example', []);
app.directive('requireiftrue', function ($compile) {
return {
require: '?ngModel',
link: function (scope, el, attrs, ngModel) {
if (!ngModel) {
return;
}
if(attrs.requireiftrue==="true"){
console.log('should require');
el.attr('required', true);
el.removeAttr('requireiftrue');
$compile(el[0])(scope);
}
else{
console.log('should not require');
}
}
};
});
I should note however that now this directive is a one-off. If the model will change, the directive will not be on the element any longer to deal with it.

Instead of using a directive, use ng-init to initialize requireiftrue.
and assign this value to ng-required like ng-required="requireiftrue" as shown below. As you said you are getting the data from rest api, you can initialize requireiftrue with the value you are getting from api, instead of true or false as shown in example below.
Hope this helps you.
Updated fiddle
http://jsfiddle.net/zsrfe513/3/
<form ng-app="form-example" name='fo' class="row form-horizontal" novalidate>
<div class="control-group" ng-form="testReq">
<h3>Form invalid: {{testReq.$invalid}}</h3>
<label class="control-label" for="inputEmail">Email</label>
<div class="controls" ng-init='requireiftrue = true'>
<input id="inputEmail" placeholder="Email" ng-model="email" name='ip' ng-required='requireiftrue'>
<span ng-show="testReq.ip.$dirty && testReq.ip.$error.required">
Required.
</span>
</div>
</div>
</form>

Try:
1. adding the required directive to the input you want to apply validation to
<input id="inputEmail" class="requireiftrue" placeholder="Email"
ng-model="email" requireiftrue="true" required>
2 Defining the directive as type class and adding the directive class to the HTML input field
JS
app.directive('requireiftrue', function ($compile) {
return {
restrict: 'C',
require: '?ngModel',
.....
HTML
<input id="inputEmail" class="requireiftrue" placeholder="Email" ng-model="email" requireiftrue="true" required>
here is a update of your fiddle - http://jsfiddle.net/4fb6wg30/

You just need to add the "required" attribute to the input.
<input max="40"
requireiftrue={{defaults.validation.name}}
validNumber
id="name"
name="name"
type="text"
ng-model="test.name"
class="form-control"
required="required">

I used <input ng-required="true"> worked fine for my angular validation component.
If your using the new angular component make sure to pass in required: "#" instead of required: "="
scope: {
required: '#'
}
I also took this further and required integer and min/max validation the same way.

Related

Angular directive for range validation

I am trying to make a directive for an input to limit the value between 1-99. On the same input I also have another directive that converts the value to a percentage and am not sure if that is what is getting in the way.
The directive is simple (taken basically from the Angular website):
(function() {
'use strict';
angular
.module('app.model')
.directive('inputRange', inputRange);
function inputRange() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, elm, attrs, ctrl) {
var INTEGER_REGEXP = /^-?\d+$/;
ctrl.$validators.inputRange = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
// consider empty models to be valid
return true;
}
if (INTEGER_REGEXP.test(viewValue)) {
// it is valid
return true;
}
// it is invalid
return false;
};
}
}
}
});
And the section of html with the input field (which is paired with a slider):
<form name="form">
<div sc-slider
ng-model="vm.baseline"
min="0.01"
max="0.99"
initial="{{vm.baseline}}"
step="0.01"
uib-tooltip="The initial estimate of the KIQ's likelihood - prior to any indicator observations."
tooltip-popup-delay="200"
tooltip-popup-close-delay="200"
tooltip-placement="bottom"></div>
<input to-percent
input-range
name="baseline"
style="text-align:center;"
type="text"
min="1"
max="99"
class="form-control"
ng-model="vm.baseline"></input>
<span ng-show="form.baseline.$error.inputRange">The value is not a valid integer!</span>
<span ng-show="form.baseline.$error.min || form.baseline.$error.max">
The value must be in range 1 to 99!</span>
</form>
I have read on SO about priority for directives that share an input but I don't think that is necessarily an issue here. But when I enter a value greater than 99 I'd expect one of the spans below to show up, but nothing is appearing. And my other directive works fine all of the time. Any help is appreciated.
Make your life easier and use ng-max and ng-min attributes on the input element.

$watch does not fire autocomplete ng-model

I have small portion of codes with $watch but does not fire when a use input with autocomplete ( jQuery plugin). it only fires when manual typing input
app.directive("autoCode", ['elementData', function(elementData) {
var codes = elementData.map(function(ele){
return ele.Code;
});
return {
restrict: 'A',
scope:{
},
link: function(scope, element, attrs) {
$(element).autocomplete({source:[codes]});
}};
}]);
app.controller('transactionCtrl',['$scope','elementData', function($scope, elementData){
var names = elementData.map(function(ele) {
return ele.Name;
}),
codes = elementData.map(function(ele) {
return ele.Code;
});
$scope.$watch('code', function(codeValue){
console.log(codeValue);
});
}]);
below is html:
<form >
Code: <input type="text" name="code" auto-code ng-model="code">
Name: <input type="text" name="name" auto-name ng-model="name">
</form>
How to make it work with manual typing and autocomplete?
Try:
$scope.$watch($("input[name='code']").length, function(codeValue){
console.log(codeValue);
});
O something like that. You have to put the watch over a value that is changed for many times

How to create a custom angular directive that will override ng-disabled?

First some background: My application allows users to control whether or not fields are required, disabled, etc. through an admin tool. I have a service that takes a field name and returns me the user defined rules in a format like this:
{
"disabled" : true,
"required" : true
}
I want a custom attribute directive that will control these properties on an input field using the service. I would expect the usage to look something like this:
<input type="text" my-rule="fieldName" ng-model="myfield" />
I'm able to accomplish this easily with a directive like the following:
angular.module('app').directive('myRule', ['$http',
function($http) {
return {
restrict: 'A',
scope: {
myRule: '#'
},
link: function(scope, element, attrs) {
$http.get('/api/rule/'+scope.myRule).success(function(rule) {
if (rule.disabled) {
element.attr('disabled','disabled');
}
if (rule.required) {
element.attr('required','required');
}
});
}
}
}
]);
My problem is that if a user does not have a field disabled I may still want to disable it until, for example, another field has been filled out. This should be easy to do with ng-disabled:
<input type="text" my-rule="fieldA" ng-model="fieldA" />
<input type="text" my-rule="fieldB" ng-model="fieldB" ng-disabled="!fieldA" />
However, this does not work because if the user chooses to disable fieldB then the field should always be disabled regardless of the ng-disabled attribute but instead the ng-disabled attribute overrides the user's rule. I tried something like this to remove the ng-disabled if the field is disabled by the user but that does not seem to have an effect:
angular.module('app').directive('myRule', ['$http',
function($http) {
return {
restrict: 'A',
scope: {
myRule: '#'
},
link: function(scope, element, attrs) {
$http.get('/api/rule/'+scope.myRule).success(function(rule) {
if (rule.disabled) {
element.attr('disabled','disabled');
element.removeAttr('ng-disabled');
}
if (rule.required) {
element.attr('required','required');
element.removeAttr('ng-required');
}
});
}
}
}
]);
This removes the attribute but it seems at that point it is too late and the field still becomes enabled as soon as fieldA is filled in.
How can I dynamically remove the ng-disabled attribute in my custom directive so that it no longer has an effect on the field?
Update:
I added a code snippet demonstrating my problem.
angular.module('app',[]).directive('myRule', ['$http',
function($http) {
return {
restrict: 'A',
scope: {
myRule: '#'
},
link: function(scope, element, attrs) {
// would normally be making an ajax call to get the rule
var rule = { disabled: scope.myRule != "fieldA" };
if (rule.disabled) {
element.attr('disabled','disabled');
element.removeAttr('ng-disabled');
}
}
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<p>Field B and Field C have been disabled by the user but since Field C includes an ng-disabled expression it will be incorrectly enabled when Field A is filled out.</p>
<input type="text" my-rule="fieldA" ng-model="fieldA" placeholder="Field A" />
<input type="text" my-rule="fieldB" ng-model="fieldB" placeholder="Field B" />
<input type="text" my-rule="fieldC" ng-model="fieldC" placeholder="Field C" ng-disabled="!fieldA" />
</div>
Tryprop('disabled', boolean) instead of attr(). This will change the element property which is not always the same as the attribute.
Since you are manipulating the DOM outside of angular you should probably tell angular to run a digest also by calling scope.$apply() or $timeout()
Not sure this will work and I think you will probably need a directive to wrap the whole input.
One suggestion is take a look at angular-formly which builds whole forms including conditional validation from object models

Clear data in angular form

I have the following angular form:
<form name="client_form" id="client_form" role="form">
<div class="bb-entry">
<label for="firstname">First Name:*</label>
<input type="text" name="firstname" ng-model="client.first_name" required class="form-control"/>
</div>
<div class="bb-entry">
<label for="lasttname">Last Name:*</label>
<input type="text" name="lastname" ng-model="client.last_name" required class="form-control"/>
</div>
<div class="bb-entry">
<label for="email">E-mail:*</label>
<input type="email" name="email" ng-model="client.email" required class="form-control"/>
</div>
</form>
<button type="button" ng-click="resetForm(client_form)">Clear</button>
I would like to add behaviour so that when users select 'Clear', all form data is cleared. I've written this method at present:
resetForm: (form) ->
form.submitted = false
form.$setPristine()
angular.copy({}, client)
However, this clears the entire client object, when really, I only want to clear the attributes referenced in my form.
I realise I can iterate around each attribute of the form object, which gives me access to the ngModelController instances as such:
resetForm: (form,) ->
form.submitted = false
form.$setPristine()
angular.forEach form, (value, key) ->
if value.hasOwnProperty("$modelValue")
# set model value here?
But can I actually assign the model value here or would a different approach be better?
I think you need to copy the client first, then clear the new client object.
Here is a fiddle link that does something very similar: http://jsfiddle.net/V44fQ/
$scope.editClient = function(client) {
$scope.edit_client = angular.copy(client);
}
$scope.cancelEdit = function() {
$scope.edit_client = {};
};
<form name="client_form" id="client_form" role="form">
<div class="bb-entry">
<label for="firstname">First Name:*</label>
<input type="text" name="firstname" ng-model="edit_client.first_name" required class="form-control">
</div>
...
<button type="button" ng-click="cancelEdit()">Clear</button>
</form>
I solved the problem by writing two directives, one that is attached to the form and the other to each individual input that I want to be 'resettable'. The directive attached to the form then adds a resetForm() method to the parent controller:
# Adds field clearing behaviour to forms.
app.directive 'bbFormResettable', ($parse) ->
restrict: 'A'
controller: ($scope, $element, $attrs) ->
$scope.inputs = []
$scope.resetForm = () ->
for input in $scope.inputs
input.getter.assign($scope, null)
input.controller.$setPristine()
registerInput: (input, ctrl) ->
getter = $parse input
$scope.inputs.push({getter: getter, controller: ctrl})
# Registers inputs with the bbFormResettable controller allowing them to be cleared
app.directive 'bbResettable', () ->
restrict: 'A',
require: ['ngModel', '^bbFormResettable'],
link: (scope, element, attrs, ctrls) ->
ngModelCtrl = ctrls[0]
formResettableCtrl = ctrls[1]
formResettableCtrl.registerInput(attrs.ngModel, ngModelCtrl)
Probably overkill, but this solution ensures only the attributes specified are cleared, not the entire client object.
No need to complicate things. Just clean the scope variables in your controller.
Plain JS:
$scope.resetForm = function() {
$scope.client.first_name = '';
$scope.client.last_name = '';
$scope.client.email = '';
}
If you wish, you can parse the $scope.client object and set properties to false (if the form is dynamic, for example).
Here is a simple example: http://jsfiddle.net/DLL3W/

Angularjs initial form validation with directives

I have a validation directive called valid-number that is used to set the validity of a form using $setValidity - this works fine for any text values that I type into the input box that have the directive applied to as an attribute.
The HTML is
<form name="numberForm">
<input name="amount" type="text" ng-model="amount" required valid-number /></form>
The directive is as follow
angular.module('test',[]).directive('validNumber',function(){
return{
require: "ngModel",
link: function(scope, elm, attrs, ctrl){
var regex=/\d/;
ctrl.$parsers.unshift(function(viewValue){
var floatValue = parseFloat(viewValue);
if(regex.test(viewValue)){
ctrl.$setValidity('validNumber',true);
}
else{
ctrl.$setValidity('validNumber',false);
}
return viewValue;
});
}
};
});
However, I would also like the validation to be triggered and set the css to an invalid clsss if the value the input box is initialised to when the page is first loaded is invalid, eg if I set $scope.amount = 'not a number' I would expect the input box to have had the directive applied to it, but no joy. In order for not a number to be highlighted as invalid I have to make a change to the contents of the input, which triggers the directive.
How can I ensure the directive applies to whatever the <input> is initialised with?
A full code example is here;
http://jsfiddle.net/JW43C/5/
$parsers array contains a list of functions that will be applied to the value that model receives from the view (what user types in), and $formatters array contains the list of functions that are being applied to the model value before it's displayed in the view.
In your directive you correctly used the $parsers array, but you also need to add the $formatters array if you want the initial value to be validated:
angular.module('test',[]).directive('validNumber',function(){
return{
require: "ngModel",
link: function(scope, elm, attrs, ctrl){
var regex = /^\d$/;
var validator = function(value){
ctrl.$setValidity('validNumber', regex.test(value));
return value;
};
ctrl.$parsers.unshift(validator);
ctrl.$formatters.unshift(validator);
}
};
});
Demo plunker
You can simply call your verification function during the linking phase, like in this fiddle :
link: function(scope, elm, attrs, ctrl) {
var regex=/\d/;
var verificationFunction = function(viewValue) {
var floatValue = parseFloat(viewValue);
if(regex.test(viewValue)) {
ctrl.$setValidity('validNumber',true);
return viewValue;
}
else {
ctrl.$setValidity('validNumber',false);
return undefined;
}
};
ctrl.$parsers.unshift(verificationFunction);
verificationFunction();
}
After (>=) angular 1.3.1 version was released you could implement that behaviour with a little bit correct way, following angular validation directives style (e.g. required, maxlength).
In that case you have to append your validator as property of $validators array and there are no need in $parsers or $formatters anymore:
var app = angular.module('test', []);
app
.directive('validNumber', function() {
return {
require: "ngModel",
link: function(scope, elm, attrs, ctrl) {
var regex = /^\d+$/;
ctrl.$validators['validNumber'] = function(modelValue, viewValue) {
return regex.test(viewValue);
};
}
};
});
app.controller('NumberCtrl', NumberCtrl);
function NumberCtrl($scope) {
$scope.amount = '5z';
};
input.ng-invalid {
background-color: #FA787E;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js"></script>
<div ng-app="test">
<div ng-controller="NumberCtrl">
<div ng-form name="numberForm">
<input name="amount"
type="text"
ng-model="amount"
required
valid-number />
<span ng-show="numberForm.amount.$error.validNumber">
Doesn't look like an integer
</span>
</div>
</div>
</div>

Categories

Resources