How to get min and max values of input angularjs - javascript

currently I'm using a custom directive which wraps the ng-minlength and ng-maxlength directives to apply the values of these directives to the model of the input. I need to do this because I'm creating a validation service which uses the angular $error object on a form to return a user friendly message about what's wrong. The problem is, when it comes to min and max lengths, I want to be able to tell the user what the length should be. I've got this working by using the following method
directive('minlength', ['$compile', function ($compile) {
return {
restrict: 'A',
require: 'ngModel',
compile: function compile(tElement) {
tElement.attr('ng-minlength', tElement.attr('minlength'));
tElement.removeAttr('minlength');
return {
post: function postLink(scope, elem, attrs) {
var keys,
form,
field = attrs['ngModel'];
$compile(elem)(scope);
for (keys in scope) {
if (scope.hasOwnProperty(keys)) {
if (keys.substring(0, 2).indexOf('$') < 0) {
if (keys !== 'this') {
form = keys;
break;
}
}
}
}
if (form) {
console.log(attrs);
scope[form][field]['minlength'] = attrs['minlength'];
}
}
}
}
}
}])
But this seems a bit longhanded and possibly difficult to maintain and test. Is there a better way to do this?

If you want just to inform the user about the min and the max:
var app = angular.module('myApp',[]);
app.controller('MyCtrl',function($scope){
$scope.minValue = 5;
$scope.maxValue = 10;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<form name="myForm">
Last name: <input type="text" name="lastName" ng-model="user.last"
ng-minlength="{{minValue}}" ng-maxlength="{{maxValue}}">
<span class="error" ng-show="myForm.lastName.$error.minlength">
Should be {{minValue}} charachters long</span>
<span class="error" ng-show="myForm.lastName.$error.maxlength">
No more than {{maxValue}} characters please</span><br>
</form>
</div>
</div>

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.

AngularJS currency filter on input field

I have the following input field
<input type="text" class="form-control pull-right" ng-model="ceremony.CeremonyFee | number:2">
it is showing up correctly but has been disabled. The error I am receiving is "[ngModel:nonassign] Expression 'ceremony.CeremonyFee | number:2' is non-assignable". I understand why it is in error, but do not know how to get this to work on an input field. Thanks.
input with ng-model is for inputting data, number filter is for displaying data. As filter values are not bindable, they are not compatible, as you can see. You have to decide what you want to do with that input.
Do you want it to be an input? User can input his own number and you only needs to validate? Use i.e. pattern attribute:
<input type="text" ng-model="ceremony.CeremonyFee" pattern="[0-9]+(.[0-9]{,2})?">
Do you want it to be an output? User does not need to input his own value? Do not use ng-model, use value instead:
<input type="text" value="{{ceremony.CeremonyFee | number:2}}" readonly>
UPDATE:
really I don't understand what you need, but, if you want just that users can insert only two digits you should use a simple html attributes, have a look on min, max, step...
Follows a pure js solution, but I don't suggest something like that!
angular.module('test', []).controller('TestCtrl', function($scope) {
var vm = $scope;
var testValue = 0;
Object.defineProperty(vm, 'testValue', {
get: function() { return testValue; },
set: function(val) {
val = Number(val);
if(angular.isNumber(val) && (val < 100 && val > 0)) {
console.log(val);
testValue = val;
}
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<section ng-app="test">
<div ng-controller="TestCtrl">
<input style="display:block; width: 100%; padding: 1em .5em;" type="number" ng-model="testValue" />
</div>
</section>
the ng-model directive requires a viewmodel assignable (or bindable) property, so, you cannot add a pipe...
angular.module('test', [])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-init="testValue = 0">
<label ng-bind="testValue | currency"></label>
<input style="display:block;" ng-model="testValue" type="number"/>
</div>
As an error states you have got an 'non-assignable' expression in your ng-model attribute.
You should use only ceremony.CeremonyFee.
| is used on ng-repeat to indicate what expression should be used as filter.
If you want to have that <input> populated with initial data in your controller/link you should give it an initial value ex.
$scope.ceremony = {
CeremonyFee: 'My first ceremony'
}
And every time your <input> element data will be changed CeremonyFee will be updated as well.
I found and used the solution found on this page.
http://jsfiddle.net/k7Lq0rns/1/
'use strict';
angular.module('induction').$inject = ['$scope'];
angular.module('induction').directive('format',['$filter', function ($filter) {
  return {
require: '?ngModel',
link: function (scope, elem, attrs, ctrl) {
if (!ctrl) return;
ctrl.$formatters.unshift(function (a) {
return $filter(attrs.format)(ctrl.$modelValue)
});
elem.bind('blur', function(event) {
var plainNumber = elem.val().replace(/[^\d|\-+|\.+]/g, '');
elem.val($filter(attrs.format)(plainNumber));
});
}
  };
}]);
relatively easy to apply it.

ngModel formatters change the $scope

Having an input with ngModel which has $formatters and $parsers -
link: function(scope, element, attrs, ngModel) {
//format text going to user (model to view)
ngModel.$formatters.push(function(obj) {
return obj.first;
});
}
After I make any change in the input , I miss this model on the scope , mean - {{person.first}} display nothing .
Here is the full code -
var myAppModule = angular.module('myApp', []);
myAppModule.controller('myCtrl',function ($scope) {
$scope.person = {
first: 'Alice',
last: 'Bob'
}
})
.directive('myDrtv',function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
var _ngModel = ngModel;
//format text going to user (model to view)
ngModel.$formatters.push(function(obj) {
return obj.first;
});
//format text from the user (view to model)
ngModel.$parsers.push(function(value) {
return value;
});
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myCtrl">
Name : <input my-drtv ng-model="person" type="text"><br>
{{person.first}}
</div>
</div>
How could I change the input and still see it in {{person.first}} ?
Please don't answer me change it to ng-model="person.first" I looking solution with - ng-model="person"
Change your input to the following:
<input my-drtv ng-model="person.first" type="text">
They way you have it, you are clobbering person and changing it from an object to a string.
Edit: If you want to keep first name and last name separate, then do something like this:
<input my-drtv ng-model="fullname" type="text">
and then in link, watch for changes and update person:
scope.$watch('fullname', function(nv) {
person.first = extractFirstName(nv);
person.last = extractLastName(nv);
});
(I left it to you to supply the extract functions).

angular: Validate multiple dependent fields

Let's say I have the following (very simple) data structure:
$scope.accounts = [{
percent: 30,
name: "Checking"},
{ percent: 70,
name: "Savings"}];
Then I have the following structure as part of a form:
<div ng-repeat="account in accounts">
<input type="number" max="100" min="0" ng-model="account.percent" />
<input type="text" ng-model="account.name" />
</div>
Now, I want to validate that the percents sum to 100 for each set of accounts, but most of the examples I have seen of custom directives only deal with validating an individual value. What is an idiomatic way to create a directive that would validate multiple dependent fields at once? There are a fair amount of solutions for this in jquery, but I haven't been able to find a good source for Angular.
EDIT: I came up with the following custom directive ("share" is a synonym for the original code's "percent").
The share-validate directive takes a map of the form "{group: accounts, id: $index}" as its value.
app.directive('shareValidate', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
params = angular.copy(scope.$eval(attr.shareValidate));
params.group.splice(params.id, 1);
var sum = +viewValue;
angular.forEach(params.group, function(entity, index) {
sum += +(entity.share);
});
ctrl.$setValidity('share', sum === 100);
return viewValue;
});
}
};
});
This ALMOST works, but can't handle the case in which a field is invalidated, but a subsequent change in another field makes it valid again. For example:
Field 1: 61
Field 2: 52
If I take Field 2 down to 39, Field 2 will now be valid, but Field 1 is still invalid. Ideas?
Ok, the following works (again, "share" is "percent"):
app.directive('shareValidate', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(attr.shareValidate, function(newArr, oldArr) {
var sum = 0;
angular.forEach(newArr, function(entity, i) {
sum += entity.share;
});
if (sum === 100) {
ctrl.$setValidity('share', true);
scope.path.offers.invalidShares = false;
}
else {
ctrl.$setValidity('share', false);
scope.path.offers.invalidShares = true;
}
}, true); //enable deep dirty checking
}
};
});
In the HTML, set the attribute as "share-validate", and the value to the set of objects you want to watch.
You can check angularui library (ui-utility part). It has ui-validate directive.
One way you can implement it then is
<input type="number" name="accountNo" ng-model="account.percent"
ui-validate="{overflow : 'checkOverflow($value,account)' }">
On the controller create the method checkOverflow that return true or false based on account calculation.
I have not tried this myself but want to share the idea. Read the samples present on the site too.
I have a case where I have a dynamic form where I can have a variable number of input fields on my form and I needed to limit the number of input controls that are being added.
I couldn't easily restrict the adding of these input fields since they were generated by a combination of other factors, so I needed to invalidate the form if the number of input fields exceeded the limit. I did this by creating a reference to the form in my controller ctrl.myForm, and then each time the input controls are dynamically generated (in my controller code), I would do the limit check and then set the validity on the form like this: ctrl.myForm.$setValidity("maxCount", false);
This worked well since the validation wasn't determined by a specific input field, but the overall count of my inputs. This same approach could work if you have validation that needs to be done that is determined by the combination of multiple fields.
For my sanity
HTML
<form ng-submit="applyDefaultDays()" name="daysForm" ng-controller="DaysCtrl">
<div class="form-group">
<label for="startDate">Start Date</label>
<div class="input-group">
<input id="startDate"
ng-change="runAllValidators()"
ng-model="startDate"
type="text"
class="form-control"
name="startDate"
placeholder="mm/dd/yyyy"
ng-required
/>
</div>
</div>
<div class="form-group">
<label for="eEndDate">End Date</label>
<div class="input-group">
<input id="endDate"
ng-change="runAllValidators()"
ng-model="endDate"
type="text"
class="form-control"
name="endDate"
placeholder="mm/dd/yyyy"
ng-required
/>
</div>
</div>
<div class="text-right">
<button ng-disabled="daysForm.$invalid" type="submit" class="btn btn-default">Apply Default Dates</button>
</div>
JS
'use strict';
angular.module('myModule')
.controller('DaysCtrl', function($scope, $timeout) {
$scope.initDate = new Date();
$scope.startDate = angular.copy($scope.initDate);
$scope.endDate = angular.copy($scope.startDate);
$scope.endDate.setTime($scope.endDate.getTime() + 6*24*60*60*1000);
$scope.$watch("daysForm", function(){
//fields are only populated after controller is initialized
$timeout(function(){
//not all viewalues are set yet for somereason, timeout needed
$scope.daysForm.startDate.$validators.checkAgainst = function(){
$scope.daysForm.startDate.$setDirty();
return (new Date($scope.daysForm.startDate.$viewValue)).getTime() <=
(new Date($scope.daysForm.endDate.$viewValue)).getTime();
};
$scope.daysForm.endDate.$validators.checkAgainst = function(){
$scope.daysForm.endDate.$setDirty();
return (new Date($scope.daysForm.startDate.$viewValue)).getTime() <=
(new Date($scope.daysForm.endDate.$viewValue)).getTime();
};
});
});
$scope.runAllValidators = function(){
//need to run all validators on change
$scope.daysForm.startDate.$validate();
$scope.daysForm.endDate.$validate();
};
$scope.applyDefaultDays = function(){
//do stuff
}
});
You can define a single directive that is only responsible for this check.
<form>
<div ng-repeat="account in accounts">
<input type="number" max="100" min="0" ng-model="account.percent" />
<input type="text" ng-model="account.name" />
</div>
<!-- HERE IT IS -->
<sum-up-to-hundred accounts="accounts"></sum-up-to-hundred>
</form>
And here's the simple directive's code.
app.directive('sumUpToHundred', function() {
return {
scope: {
accounts: '<'
},
require: {
formCtrl: '^form'
},
bindToController: true,
controllerAs: '$ctrl',
controller: function() {
var vm = this;
vm.$doCheck = function(changes) {
var sum = vm.accounts.map((a)=> a.percent).reduce((total, n)=> total + n);
if (sum !== 100) {
vm.formCtrl.$setValidity('sumuptohundred', false);
} else {
vm.formCtrl.$setValidity('sumuptohundred', true);
}
};
}
};
});
Here's a plunker.

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