6
votes

I am working on one requirement where I want to allow only even numbers to text box or number box(input type number). with minimum and maximum limit like from 4 to 14 and it should only increase by step of 2 if we have number box.

I tried with HTML input type number with min max and step attributes it's working fine but we can edit the text box with any number so to restrict I tried using directive but it's not working out for me. I will be glad if anyone can help me out with this.

HTML :

<body ng-controller="ctrl">

new : <number-only-input step="2" min="4" max="14" input-value="wks.number" input-name="wks.name" >

</body>

Script :

var app = angular.module('app', []);
app.controller('ctrl', function($scope){
    $scope.name = 'Samir Shah';
    $scope.price = -10;
    $scope.wks =  {number: '', name: 'testing'};
});

app.directive('numberOnlyInput', function () {
    return {
        restrict: 'EA',
        template: '<input type="text" name="{{inputName}}" ng-model="inputValue" />',
        scope: {
            inputValue: '=',
            inputName: '=',
            min: '@',
            max: '@',
            step: '@'
        },
        link: function (scope) {
            scope.$watch('inputValue', function(newValue,oldValue) {
                var arr = String(newValue).split("");
                if (arr.length === 0) return;
                if (arr.length === 1 && (arr[0] == '-' || arr[0] === '.' )) return;
                if (arr.length === 2 && newValue === '-.') return;
                if (isNaN(newValue)) {
                    scope.inputValue = oldValue;
                    return;
                }
                if(!isNaN(newValue)){
                        if(newValue < parseInt(scope.min) || newValue > parseInt(scope.max)){
                            scope.inputValue = oldValue;
                            return;
                        }
                }
            });
        }
    };
});

3
@Vineet validation is fine. as I need to have only even numbers but the text number is editable to any number. - Samir Shah
Why don't you use readonly = "readonly" ? - Vineet
readonly will not show increment decrement button of input type number. - Samir Shah
on your watch can use modulo operator (%). If (newValue%step != 0) newValue = oldValue; - nada

3 Answers

1
votes
<form name="testForm">
<div ng-controller="MyCtrl">
    <input type="text" name="testInput" ng-model="number" ng-min="2" ng-max="14" required="required" numbers-only="numbers-only" />
    <div ng-show="testForm.testInput.$error.nonnumeric" style="color: red;">
        Numeric input only.
    </div>
    <div ng-show="testForm.testInput.$error.belowminimum" style="color: red;">
        Number is too small.
    </div>
    <div ng-show="testForm.testInput.$error.abovemaximum" style="color: red;">
        Number is too big.
    </div>
    <div ng-show="testForm.testInput.$error.odd" style="color: red;">
        Numeric is odd.
    </div>
</div>
</form>

angular.module('myApp', []).directive('numbersOnly', function () {
return {
    require: 'ngModel',
    link: function (scope, element, attrs, modelCtrl) {
        element.bind('blur', function () {
            if (parseInt(element.val(), 10) < attrs.ngMin) {
                modelCtrl.$setValidity('belowminimum', false);
                scope.$apply(function () {
                    element.val('');
                });
            }
        });
        modelCtrl.$parsers.push(function (inputValue) {
            // this next if is necessary for when using ng-required on your input. 
            // In such cases, when a letter is typed first, this parser will be called
            // again, and the 2nd time, the value will be undefined
            if (inputValue == undefined) return ''
            var transformedInput = inputValue.replace(/[^0-9]/g, '');
            if (transformedInput != inputValue || (parseInt(transformedInput, 10) < parseInt(attrs.ngMin, 10) && transformedInput !== '1') || parseInt(transformedInput, 10) > parseInt(attrs.ngMax, 10) || (transformedInput % 2 !== 0 && transformedInput !== '1')) {
                if (transformedInput != inputValue) {
                    modelCtrl.$setValidity('nonnumeric', false);
                } else {
                    modelCtrl.$setValidity('nonnumeric', true);
                }
                if (parseInt(transformedInput, 10) < parseInt(attrs.ngMin, 10) && transformedInput !== '1') {
                    modelCtrl.$setValidity('belowminimum', false);
                } else {
                    modelCtrl.$setValidity('belowminimum', true);
                }
                if (parseInt(transformedInput, 10) > parseInt(attrs.ngMax, 10)) {
                    modelCtrl.$setValidity('abovemaximum', false);
                } else {
                    modelCtrl.$setValidity('abovemaximum', true);
                }
                if (transformedInput % 2 !== 0 && transformedInput !== '1') {
                    modelCtrl.$setValidity('odd', false);
                } else {
                    modelCtrl.$setValidity('odd', true);
                }
                transformedInput = '';
                modelCtrl.$setViewValue(transformedInput);
                modelCtrl.$render();
                return transformedInput;
            }
            modelCtrl.$setValidity('nonnumeric', true);
            modelCtrl.$setValidity('belowminimum', true);
            modelCtrl.$setValidity('abovemaximum', true);
            modelCtrl.$setValidity('odd', true);
            return transformedInput;
        });
    }
};
});

Active fiddle http://jsfiddle.net/tuckerjt07/1Ldmkmog/

0
votes

You could define a property with getter and setter to process the entered value. If the value does not match the requrements display messages but not accept new value.

Using this method you could apply any validation logic, the second field editValue is needed because otherwise you could not enter an invalid number. Therefore editValue alows to enter numbers with numerous digits which will be partially invalid during entering the value.

Property:

  // Property used to bind input containing validation
  Object.defineProperty($scope, "number", {
    get: function() {
      return $scope.editValue;
    },
    set: function(value) {
      value = parseInt(value);
      $scope.editValue = value;
      var isValid = true;
      // Min?
      if (value < parseInt($scope.min)) {
        $scope.toSmall = true;
        isValid = false;
      } else {
        $scope.toSmall = false;
      }
      // Max?
      if (value > parseInt($scope.max)) {
        $scope.toBig = true;
        isValid = false;
      } else {
        $scope.toBig = false;
      }
      // Step not valid
      if (value % parseInt($scope.step) > 0) {
        $scope.stepNotValid = true;
        isValid = false;
      } else {
        $scope.stepNotValid = false;
      }
      $scope.isValid = isValid;
      if (isValid) {
        $scope.value = value;
      }
    }
  });


Working example

Below you can find a complete working example directive containing the property described above including increase/decrease buttons:

var app = angular.module('myApp', []);

app.directive('numberOnlyInput', function() {
  return {
    restrict: 'E',
    template: '<input type="text" ng-model="number" ng-class="{\'error\': !isValid}"/><button ng-click="increase()">+</button><button ng-click="decrease()">-</button> Value: {{value}} {{stepNotValid ? (" value must be in steps of " + step) : ""}} {{toSmall ? " value must be greater or equal to " + min : ""}} {{toBig ? " value must be smaler or equal to " + max : ""}}',
    scope: {
      value: '=value',
      min: '@',
      max: '@',
      step: '@'
    },
    link: function($scope) {
      // Increase value
      $scope.increase = function() {
        var newValue = parseInt($scope.value) + parseInt($scope.step);
        if (newValue <= $scope.max) {
          $scope.number = newValue;
          $scope.editValue = $scope.number;
        }
      };
      // Decrease value
      $scope.decrease = function() {
        var newValue = parseInt($scope.value) - parseInt($scope.step);
        if (newValue >= $scope.min) {
          $scope.number = newValue;
          $scope.editValue = $scope.number;
        }
      };
      // Property used to bind input containing validation
      Object.defineProperty($scope, "number", {
        get: function() {
          return $scope.editValue;
        },
        set: function(value) {
          value = parseInt(value);
          $scope.editValue = value;
          var isValid = true;
          // Min?
          if (value < parseInt($scope.min)) {
            $scope.toSmall = true;
            isValid = false;
          } else {
            $scope.toSmall = false;
          }
          // Max?
          if (value > parseInt($scope.max)) {
            $scope.toBig = true;
            isValid = false;
          } else {
            $scope.toBig = false;
          }
          // Step not valid
          if (value % parseInt($scope.step) > 0) {
            $scope.stepNotValid = true;
            isValid = false;
          } else {
            $scope.stepNotValid = false;
          }
          $scope.isValid = isValid;
          if (isValid) {
            $scope.value = value;
          }
        }
      });
      // Init actual Value of the input element
      $scope.number = parseInt($scope.value);
      $scope.editValue = parseInt($scope.value);
    }
  };
});
app.controller('controller', function($scope) {
  $scope.value = 10;
});
.error {
  color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="controller">
  Number:
  <number-only-input min="4" max="14" step="2" value="value"></number-only-input>
</div>
0
votes

Why are you doing too much of work of a simple thing. Max length will not work with <input type="number" the best way I know is to use oninput event to limit the maxlength. Please see the below code, Its a generic solution work with all the Javascript framework.

<input name="somename"
    oninput="javascript: if (this.value.length > this.maxLength) this.value = this.value.slice(0, this.maxLength);"
    type = "number"
    maxlength = "6"
 />