preventing maxLength on a input type number - angularjs

I took me a while to figure this one out, but can someone post a cleaner method for limiting the number of digits in an input type='number'. One of the issues is that errors are thrown if $scope.variable = null....meaning nothing in the input field.
<input type="number" model='modalName' ng-change="monitorLength('modalName',16)">
JS:
$scope.monitorLength = function (model,maxLength) {
if ($scope[model] != null) { // prevent error on empty input field
var len = $scope[model].toString() ; // convert to a string
if (len.length > maxLength) { //evaluate string length
$scope[model] = parseInt(len.substring(0, maxLength));
// convert back to number or warning is thrown on input value not being a number
}
}
}
I then needed to expand up on this to account for number only, preventing any non-digit characters include '.' and ',' symbols:
var reg = new RegExp(/^\d+$/) ;
$scope.monitorLength = function (modal,maxLength) {
if ($scope[modal] != null) {
var len = $scope[modal].toString() ;
if (len.length > maxLength) {
$scope[modal] = parseInt(len.substring(0, maxLength));
} else if (!reg.test(len)) {
$scope[modal] = parseInt(len.substring(0, len.length-2));
}
}
}
Is there way to extract the ng-modal that was responsible for calling the ng-change? so the call would only have to be: ng-change="monitorLength(10)". And then in the function somehow dynamically retrieve the calling ng-modal?

<input type="number" max="99" onkeypress="if (this.value.length >= 2) return false;"/>
OR
<!--maxlength="10"-->
<input type="number" onKeyPress="if(this.value.length==10) return false;" />

this is a cleaner method for limiting the number, using ngMaxlength for that:
<input type="number" model='modalName' ng-maxlength="16">
You can find more attributes and info here

Is there way to extract the ng-modal that was responsible for calling the ng-change?
Yes. You can define a directive and require the ngModelController.
.directive('maxNum', function(){
return {
require: '^ngModel',
link: function($scope, elem, attrs){
// here you can add formatters/parsers to the ngModel
// to affect the change on the ngModel.$viewValue.
}
}
})
As #rolinger stated on the other answer, using the built in directives will not prevent the use from entering non-valid characters, they simply mark the model as being invalid.

Related

chrome.storage.local.get iterate simultaneously through keys and elements

First of all, sorry if the original question isn't clear enough,
I was struggling to define the exact problem I'm having.
I am making a Chrome extension which features a list of blank input boxes. I would like to save the value assigned in those input boxes using the chrome.storage.set method, and retrieve said values into their original input boxes when the popup is reopened.
So far, I have managed to store locally the values of the boxes using a loop, assigning each value a key depending of its order of iteration.
HTML
<input type="text" class="random" value="">
<input type="text" class="random" value="">
<input type="text" class="random" value="">
<button id="4">save</button>
JS
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("4").addEventListener("click", save);
});
function save() {
var id = document.querySelectorAll("input[type='text']");
for (i = 0; i < id.length; i++) {
var inputValue= id[i].value;
if (id.length > 0) {
var key = "key"+i;
chrome.storage.local.set({[key]: inputValue});
alert(key)}
}
}
The problem comes when I try to retrieve each value and return it to its original input field. My solution was to create another loop which iterates through the input fields while retrieving the corresponding keys, but can't seem to make it work.
window.onload = () => {
const id = document.querySelectorAll("input[type='text']");
for (i = 0; i < id.length; i++) {
if (id.length > 0) {
var key = "key"+i;
chrome.storage.local.get([key], (data) => {
if (data.key) {
id[i].value = data.key;
}
});
}
}}
How should I define the variables properly? Is there any other work around to achieve the same result?

Angularjs: how to detect min and max value of input numbers

I am trying to detect the minimum and maximum values of number type input field, so if the user increased the value, an ajax request get send, and so on for the max value.
Here is my code:
<input type="number" name="amount" id="amount" min="minNumber"
max="maxNumber" value="{{value['qty']}}"
data-product="{{value['id']}}"
ng-blur="isValid(true)"
ng-change="isValid(false)"
ng-model="level.num">
$scope.minNumber = 1,
$scope.maxNumber = 99,
$scope.addedToCart = false
$scope.isValid = function () {
if( ($scope.level.num < $scope.maxNumber) || ($scope.level.num > $scope.minNumber && blurMode)) {
$scope.tooMany = true;
$scope.level.num = $scope.minNumber;
}
};
You should not pass any parameter to your function from template, and use only ng-blur in your case no need of ng-change unless you want to check for each text change
ng-blur="isValid()"

Update view with formatted value, model with numeric value

I have a need for input boxes to display values formatted based on the user's locale but the model must store the value in en-US locale and all of this happens on blur. I've got the formatting of the fields working when the user clicks off of them but I cannot figure out how to set the model value. In my code formatedValue is being set correctly for the user to view but how do I update the model value to be "valueToFormat"? I've tried
scope.$modelValue = valueToFormat;
and it works when watching it thru the debugger but as soon as the view is rendered the value reverts to the $viewValue. How can I accomplish this?
element.bind('blur', function () {
var val = ctrl.$modelValue;
parse(val);
})
ctrl.$formatters.push(function(value) {
if(!value) {
return value;
}
var valueToFormat = getActualValue(value, decimalDelimiter, thousandsDelimiter, decimals, '%') || '0';
return viewMask.apply(prepareNumberToFormatter(valueToFormat, decimals));
});
function parse(value) {
if(!value) {
return value;
}
var valueToFormat = getActualValue(value, decimalDelimiter, thousandsDelimiter, decimals) || '0';
var formatedValue = viewMask.apply(prepareNumberToFormatter(valueToFormat, decimals));
var actualNumber = parseFloat(modelMask.apply(valueToFormat));
ctrl.$viewValue = formatedValue;
ctrl.$render();
return valueToFormat;
}
You should be able to use a filter to solve this issue. For example this is a date filter that we use. This is with typescript so a little tweaking will be necessary for straight java-script but it gets the point across.
app.filter('MyDateFilter', ['$filter', function ($filter: ng.IFilterService)
{
return function (value:any, format)
{
if (typeof value == "string")
{
var stringValue = <string>value;
//If there is no / or - then assume its not a date
if (stringValue.indexOf('/') == -1 && stringValue.indexOf('-') == -1)
return value;
}
var parsedDate = Date.parse(value);
if (isNaN(parsedDate))
return value;
else
return $filter('date')(value, format);
}
}]);
And then in the html.
<div ng-repeat="value in values" >
{{value | MyDateFilter:"MM/dd/yyyy" }}
</div>
Since it seems like you want to change an input display value. here is a solution we use for that. I created a custom directive and inside of there you can manipulte the $render. So:
app.directive("myDateDisplayInput", ['$filter', function DatepickerDirective(filter: ng.IFilterService): ng.IDirective
{
return {
restrict: 'A',
require: 'ngModel',
link: (scope: ng.IScope, element, attrs, ngModelCtrl: ng.INgModelController) =>
{
ngModelCtrl.$render = () =>
{
var date = ngModelCtrl.$modelValue ?
filter('date')(ngModelCtrl.$modelValue, "MM/dd/yyyy") :
ngModelCtrl.$viewValue;
jqueryElement.val(date);
};
}
};
}
This will format the value to be what you want. If you only want this to happen on blur then you add
ng-model-options="{ updateOn: 'blur' }"
<input type="text" my-date-display-input ng-model-options="{ updateOn: 'blur' }" ng-model="unformattedDate"/>
Some pieces might be missing but this should get the idea across.

when the validity of an input depends on the value of another input - or how to trigger validation in Angularjs?

As demonstrative in the following code sample, the input named amountOrPercet can be interpreted as an amount or as a percentage value, depending on the state of the mode radio button.
<input type="radio" name="mode" value="amt" ng-model="mode"/> Amount
<input type="radio" name="mode" value="pct"ng-model="mode"/> Percent
<input type="text" name="amountOrPercent" ng-model="amountOrPercent" check-percent/>
I have put together an attribute directive to invalidate amountOrPercent for values greater than 100, in case it must be interpreted as a percentage:
myApp.directive('checkPct', function () {
return {
require: 'ngModel',
link: function (scope, elem, attr, ngModel) {
ngModel.$parsers.unshift(function (value) {
var valid = scope.mode != 'pct' || value <= 100;
ngModel.$setValidity('checkPct', valid);
return valid ? value : undefined;
});
ngModel.$formatters.unshift(function (value) {
ngModel.$setValidity('checkPct', scope.mode != 'pct' || value <= 100);
return value;
});
}
};
});
When the value of mode it kept unchanged, The validation works as expected. However when the value of mode is changed, the value of amountOrPercent is not re-validated, unless user changes the value of amountOrPercentage.
I know that I can use a watch to do some action based on changes in mode, but my question is how I can trigger the validation on amountOrPercent, when mode is changed. - Thank you
You can add watcher for mode and revalidate value when it changes:
scope.$watch('mode', function(){
var valid = scope.mode != 'pct' || ngModel.$modelValue <= 100;
ngModel.$setValidity('checkPct', valid);
}

Why is ng-maxlength not re-evaluated?

I have a form, that has one input field and three check boxes. Depending on which check box is selected the max length on the field needs to change. I have a input field defined like this
<input placeholder="ID" type="text" id="form_ID" name="searchId" autofocus
data-ng-model="vm.searchCriteria.searchId" data-ng-required="vm.isSearchIdRequired"
data-ng-minlength="1" data-ng-maxlength="{{searchIdMaxLength}}"
data-ng-class="{'input-error': vm.isSearchIdValid}">
and one of the checkboxes
<input type="checkbox" id="checkbox1" class="hidden-field"
data-ng-model="vm.searchCriteria.searchIdInSrId" data-ng-checked="vm.searchCriteria.searchIdInSrId"
data-ng-change="processSearchIdOptionsChange()">
So everytime user changes which checkbox is/are selected processSearchIdOptionsChange gets called, and searchIdMaxLength changes it's value. This is all working fine and I can see the value being changed on the $scope. But, my initial max length is still being applied. Following error pops up after initial max number of chars is reached. Why?
<span class="error" data-ng-show="(searchForm.$dirty && searchForm.searchId.$error.maxlength)">Too long!</span>
This is the intended behaviour of ng-maxlength. Verified from source : https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js?source=c#L523
The value is parsed only once and cached :
var maxlength = int(attr.ngMaxlength);
If you want to observe the change you need to create your own directive which uses something like
scope.$watch(attr.namespaceMaxLength,function(){
// clear old validator. Add new one.
})
After a lot of trial and error here is the directive that does what I need. Please if you have any suggestions or improvements share them, I have only 7 days of angular under my belt, and javascript is something that i have a cursory knowledge of.
(function () {
'use strict';
angular.module('commonModule')
.directive('srMaxlength', ['$window', srMaxlength]);
function srMaxlength($window) {
// Usage:
// use if you need to switch max length validation dynamically based on
// Creates:
// removes old validator for max length and creates new one
var directive = {
require: 'ngModel',
link: link,
restrict: 'A'
};
return directive;
function link(scope, element, attrs, ctrl) {
attrs.$observe("srMaxlength", function (newval) {
var maxlength = parseInt(newval, 10);
var name = "srMaxLengthValidator";
for (var i = ctrl.$parsers.length - 1; i >= 0; i--) {
if (ctrl.$parsers[i].name !== undefined && ctrl.$parsers[i].name == name) {
ctrl.$parsers.splice(i, 1);
}
}
for (var j = ctrl.$formatters.length - 1; j >= 0; j--) {
if (ctrl.$formatters[j].name !== undefined && ctrl.$formatters[j].name == name) {
ctrl.$formatters.splice(j, 1);
}
}
ctrl.$parsers.push(maxLengthValidator);
ctrl.$formatters.push(maxLengthValidator);
//name the function so we can find it always by the name
maxLengthValidator.name = name;
function maxLengthValidator(value) {
if (!ctrl.$isEmpty(value) && value.length > maxlength) {
ctrl.$setValidity('maxlength', false);
return undefined;
} else {
ctrl.$setValidity('maxlength', true);
return value;
}
}
});
}
}
})();

Resources